[agent-pipeline] pipe-20260327053504-cc40 round-2

This commit is contained in:
DDD1542
2026-03-27 18:11:56 +09:00
parent 12dd49fe3d
commit 3923dbefa0
398 changed files with 64367 additions and 386 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
"/Users/gbpark/agent-pipeline/engine/build/mcp/mailbox-stdio-bridge.js"
],
"env": {
"MAILBOX_URL": "http://127.0.0.1:51824",
"MAILBOX_URL": "http://127.0.0.1:54227",
"MAILBOX_AGENT": "backend"
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
"/Users/gbpark/agent-pipeline/engine/build/mcp/mailbox-stdio-bridge.js"
],
"env": {
"MAILBOX_URL": "http://127.0.0.1:51824",
"MAILBOX_URL": "http://127.0.0.1:54227",
"MAILBOX_AGENT": "frontend"
}
}
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
{"pid":14921,"round":1,"status":"running","timestamp":"2026-03-27T02:21:23.348Z","uptimeMs":211033.79775}
{"pid":99834,"round":2,"status":"running","timestamp":"2026-03-27T09:11:35.880Z","uptimeMs":12991125.527583001}
@@ -0,0 +1,25 @@
# 실행 메트릭
| 항목 | 값 |
|------|------|
| 총 에이전트 호출 | 2회 |
| 총 소요 시간 | 353초 |
| 평균 호출 시간 | 177초 |
## 토큰 사용량
| 항목 | 값 |
|------|------|
| input_tokens | 941 |
| output_tokens | 13038 |
| cache_read_tokens | 1200300 |
| cache_write_tokens | 40613 |
| 입력 비용 | $0.0141 |
| 출력 비용 | $0.9778 |
| 총 비용 | $0.9920 |
## 호출 상세
| 에이전트 | 라운드 | 소요(s) | 입력토큰 | 출력토큰 | 시간 |
|----------|--------|---------|----------|----------|------|
| backend | 1 | 237 | 941 | 13.0K | 오전 11:21:50 |
| pm | 1 | 117 | 0 | 0 | 오전 11:23:46 |
@@ -1,57 +1,57 @@
# Pipeline: pipe-20260327021752-7ctg
- 시작: 2026. 3. 27. 오전 11:17:52
# Pipeline: pipe-20260327053504-cc40
- 시작: 2026. 3. 27. 오후 2:35:04
- 상태: **running**
- 현재 라운드: 1/25
- 경과 시간: 3분 31초
- 현재 라운드: 2/25
- 경과 시간: 216분 31초
- 성공: 0 / 실패: 0 / 전체: 102
## 태스크 현황
| 태스크 | 에이전트 | 상태 | 시도 | 검증 |
|--------|----------|------|------|------|
| task-1 | backend | 대기 | 0/5 | - |
| task-2 | backend | 대기 | 0/5 | - |
| task-3 | backend | 대기 | 0/5 | - |
| task-4 | backend | 대기 | 0/5 | - |
| task-5 | backend | 대기 | 0/5 | - |
| task-6 | backend | 대기 | 0/5 | - |
| task-7 | backend | 대기 | 0/5 | - |
| task-8 | backend | 대기 | 0/5 | - |
| task-9 | backend | 대기 | 0/5 | - |
| task-10 | backend | 대기 | 0/5 | - |
| task-11 | backend | 대기 | 0/5 | - |
| task-12 | backend | 대기 | 0/5 | - |
| task-13 | backend | 대기 | 0/5 | - |
| task-14 | backend | 대기 | 0/5 | - |
| task-15 | backend | 대기 | 0/5 | - |
| task-16 | backend | 대기 | 0/5 | - |
| task-17 | backend | 대기 | 0/5 | - |
| task-18 | backend | 대기 | 0/5 | - |
| task-19 | backend | 대기 | 0/5 | - |
| task-20 | backend | 대기 | 0/5 | - |
| task-21 | backend | 대기 | 0/5 | - |
| task-22 | backend | 대기 | 0/5 | - |
| task-23 | backend | 대기 | 0/5 | - |
| task-24 | backend | 대기 | 0/5 | - |
| task-25 | backend | 대기 | 0/5 | - |
| task-26 | backend | 대기 | 0/5 | - |
| task-27 | backend | 대기 | 0/5 | - |
| task-28 | backend | 대기 | 0/5 | - |
| task-29 | backend | 대기 | 0/5 | - |
| task-30 | backend | 대기 | 0/5 | - |
| task-31 | backend | 대기 | 0/5 | - |
| task-32 | backend | 대기 | 0/5 | - |
| task-33 | backend | 대기 | 0/5 | - |
| task-34 | backend | 대기 | 0/5 | - |
| task-35 | backend | 대기 | 0/5 | - |
| task-36 | backend | 대기 | 0/5 | - |
| task-37 | backend | 대기 | 0/5 | - |
| task-38 | backend | 대기 | 0/5 | - |
| task-39 | backend | 대기 | 0/5 | - |
| task-40 | backend | 대기 | 0/5 | - |
| task-41 | backend | 대기 | 0/5 | - |
| task-42 | backend | 대기 | 0/5 | - |
| task-43 | backend | 대기 | 0/5 | - |
| task-44 | backend | 대기 | 0/5 | - |
| task-1 | backend | 완료 | 1/5 | - |
| task-2 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-3 | backend | 완료 | 0/5 | - |
| task-4 | backend | 완료 | 0/5 | - |
| task-5 | backend | 완료 | 0/5 | - |
| task-6 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-7 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-8 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-9 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-10 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-11 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-12 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-13 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-14 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-15 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-16 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-17 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-18 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-19 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-20 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-21 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-22 | backend | 대기 | 1/5 | - |
| task-23 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-24 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-25 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-26 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-27 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-28 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-29 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-30 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-31 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-32 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-33 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-34 | backend | 대기 | 1/5 | - |
| task-35 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-36 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-37 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-38 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-39 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-40 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-41 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-42 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-43 | backend | 대기 | 1/5 | - |
| task-44 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-45 | backend | 대기 | 0/5 | - |
| task-46 | backend | 대기 | 0/5 | - |
| task-47 | backend | 대기 | 0/5 | - |
@@ -59,57 +59,60 @@
| task-49 | backend | 대기 | 0/5 | - |
| task-50 | backend | 대기 | 0/5 | - |
| task-51 | backend | 대기 | 0/5 | - |
| task-52 | backend | 대기 | 0/5 | - |
| task-53 | backend | 대기 | 0/5 | - |
| task-54 | backend | 대기 | 0/5 | - |
| task-55 | backend | 대기 | 0/5 | - |
| task-56 | backend | 대기 | 0/5 | - |
| task-57 | backend | 대기 | 0/5 | - |
| task-58 | backend | 대기 | 0/5 | - |
| task-59 | backend | 대기 | 0/5 | - |
| task-60 | backend | 대기 | 0/5 | - |
| task-61 | backend | 대기 | 0/5 | - |
| task-62 | backend | 대기 | 0/5 | - |
| task-63 | backend | 대기 | 0/5 | - |
| task-64 | backend | 대기 | 0/5 | - |
| task-65 | backend | 대기 | 0/5 | - |
| task-66 | backend | 대기 | 0/5 | - |
| task-67 | backend | 대기 | 0/5 | - |
| task-68 | backend | 대기 | 0/5 | - |
| task-69 | backend | 대기 | 0/5 | - |
| task-70 | backend | 대기 | 0/5 | - |
| task-71 | backend | 대기 | 0/5 | - |
| task-72 | backend | 대기 | 0/5 | - |
| task-73 | backend | 대기 | 0/5 | - |
| task-74 | backend | 대기 | 0/5 | - |
| task-75 | backend | 대기 | 0/5 | - |
| task-52 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-53 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-54 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-55 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-56 | backend | 대기 | 1/5 | - |
| task-57 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-58 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-59 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-60 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-61 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-62 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-63 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-64 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-65 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-66 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-67 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-68 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-69 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-70 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-71 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-72 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-73 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-74 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-75 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-76 | backend | 대기 | 0/5 | - |
| task-77 | backend | 대기 | 0/5 | - |
| task-78 | backend | 대기 | 0/5 | - |
| task-79 | backend | 대기 | 0/5 | - |
| task-80 | backend | 대기 | 0/5 | - |
| task-81 | backend | 대기 | 0/5 | - |
| task-82 | backend | 대기 | 0/5 | - |
| task-83 | backend | 대기 | 0/5 | - |
| task-84 | backend | 대기 | 0/5 | - |
| task-85 | backend | 대기 | 0/5 | - |
| task-86 | backend | 대기 | 0/5 | - |
| task-87 | backend | 대기 | 0/5 | - |
| task-88 | backend | 대기 | 0/5 | - |
| task-89 | backend | 대기 | 0/5 | - |
| task-90 | backend | 대기 | 0/5 | - |
| task-91 | backend | 대기 | 0/5 | - |
| task-78 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-79 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-80 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-81 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-82 | backend | 대기 | 1/5 | - |
| task-83 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-84 | backend | 대기 | 1/5 | - |
| task-85 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-86 | backend | 대기 | 1/5 | - |
| task-87 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-88 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-89 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-90 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-91 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-92 | backend | 대기 | 0/5 | - |
| task-93 | backend | 대기 | 0/5 | - |
| task-93 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-94 | backend | 대기 | 0/5 | - |
| task-95 | backend | 대기 | 0/5 | - |
| task-96 | backend | 대기 | 0/5 | - |
| task-97 | backend | 대기 | 0/5 | - |
| task-98 | backend | 대기 | 0/5 | - |
| task-99 | backend | 대기 | 0/5 | - |
| task-95 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-96 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-97 | backend | 대기 | 1/5 | - |
| task-98 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-99 | backend | 완료 | 1/5 | L1 통과: cd backend-spring && ./ |
| task-100 | backend | 대기 | 0/5 | - |
| task-101 | frontend | 대기 | 0/5 | - |
| task-102 | frontend | 대기 | 0/5 | - |
## PM 마지막 판단
Round 1에서 backend 에이전트가 task-1(Spring Boot 프로젝트 초기화)을 성공적으로 완료했다. L1(compileJava)과 L6(bootJar) 모두 통과. 에이전트가 task-1 범위를 넘어서 task-2~5에 해당하는 파일들(DecimalUtils+Test, ApiResponse, GlobalExceptionHandler, Se
---
마지막 업데이트: 2026. 3. 27. 오전 11:21:23
마지막 업데이트: 2026. 3. 27. 오후 6:11:35
@@ -0,0 +1,553 @@
{
"pipelineId": "pipe-20260327021752-7ctg",
"runDir": "/Users/gbpark/TEST-VEX/_pipeline/runs/2026-03-27_pipe-20260327021752-7ctg",
"lastCompletedRound": 1,
"maxRounds": 25,
"taskStatuses": [
{
"id": "task-1",
"status": "success",
"attempts": 1
},
{
"id": "task-2",
"status": "pending",
"attempts": 0
},
{
"id": "task-3",
"status": "success",
"attempts": 0
},
{
"id": "task-4",
"status": "success",
"attempts": 0
},
{
"id": "task-5",
"status": "success",
"attempts": 0
},
{
"id": "task-6",
"status": "pending",
"attempts": 0
},
{
"id": "task-7",
"status": "pending",
"attempts": 0
},
{
"id": "task-8",
"status": "pending",
"attempts": 0
},
{
"id": "task-9",
"status": "pending",
"attempts": 0
},
{
"id": "task-10",
"status": "pending",
"attempts": 0
},
{
"id": "task-11",
"status": "pending",
"attempts": 0
},
{
"id": "task-12",
"status": "pending",
"attempts": 0
},
{
"id": "task-13",
"status": "pending",
"attempts": 0
},
{
"id": "task-14",
"status": "pending",
"attempts": 0
},
{
"id": "task-15",
"status": "pending",
"attempts": 0
},
{
"id": "task-16",
"status": "pending",
"attempts": 0
},
{
"id": "task-17",
"status": "pending",
"attempts": 0
},
{
"id": "task-18",
"status": "pending",
"attempts": 0
},
{
"id": "task-19",
"status": "pending",
"attempts": 0
},
{
"id": "task-20",
"status": "pending",
"attempts": 0
},
{
"id": "task-21",
"status": "pending",
"attempts": 0
},
{
"id": "task-22",
"status": "pending",
"attempts": 0
},
{
"id": "task-23",
"status": "pending",
"attempts": 0
},
{
"id": "task-24",
"status": "pending",
"attempts": 0
},
{
"id": "task-25",
"status": "pending",
"attempts": 0
},
{
"id": "task-26",
"status": "pending",
"attempts": 0
},
{
"id": "task-27",
"status": "pending",
"attempts": 0
},
{
"id": "task-28",
"status": "pending",
"attempts": 0
},
{
"id": "task-29",
"status": "pending",
"attempts": 0
},
{
"id": "task-30",
"status": "pending",
"attempts": 0
},
{
"id": "task-31",
"status": "pending",
"attempts": 0
},
{
"id": "task-32",
"status": "pending",
"attempts": 0
},
{
"id": "task-33",
"status": "pending",
"attempts": 0
},
{
"id": "task-34",
"status": "pending",
"attempts": 0
},
{
"id": "task-35",
"status": "pending",
"attempts": 0
},
{
"id": "task-36",
"status": "pending",
"attempts": 0
},
{
"id": "task-37",
"status": "pending",
"attempts": 0
},
{
"id": "task-38",
"status": "pending",
"attempts": 0
},
{
"id": "task-39",
"status": "pending",
"attempts": 0
},
{
"id": "task-40",
"status": "pending",
"attempts": 0
},
{
"id": "task-41",
"status": "pending",
"attempts": 0
},
{
"id": "task-42",
"status": "pending",
"attempts": 0
},
{
"id": "task-43",
"status": "pending",
"attempts": 0
},
{
"id": "task-44",
"status": "pending",
"attempts": 0
},
{
"id": "task-45",
"status": "pending",
"attempts": 0
},
{
"id": "task-46",
"status": "pending",
"attempts": 0
},
{
"id": "task-47",
"status": "pending",
"attempts": 0
},
{
"id": "task-48",
"status": "pending",
"attempts": 0
},
{
"id": "task-49",
"status": "pending",
"attempts": 0
},
{
"id": "task-50",
"status": "pending",
"attempts": 0
},
{
"id": "task-51",
"status": "pending",
"attempts": 0
},
{
"id": "task-52",
"status": "pending",
"attempts": 0
},
{
"id": "task-53",
"status": "pending",
"attempts": 0
},
{
"id": "task-54",
"status": "pending",
"attempts": 0
},
{
"id": "task-55",
"status": "pending",
"attempts": 0
},
{
"id": "task-56",
"status": "pending",
"attempts": 0
},
{
"id": "task-57",
"status": "pending",
"attempts": 0
},
{
"id": "task-58",
"status": "pending",
"attempts": 0
},
{
"id": "task-59",
"status": "pending",
"attempts": 0
},
{
"id": "task-60",
"status": "pending",
"attempts": 0
},
{
"id": "task-61",
"status": "pending",
"attempts": 0
},
{
"id": "task-62",
"status": "pending",
"attempts": 0
},
{
"id": "task-63",
"status": "pending",
"attempts": 0
},
{
"id": "task-64",
"status": "pending",
"attempts": 0
},
{
"id": "task-65",
"status": "pending",
"attempts": 0
},
{
"id": "task-66",
"status": "pending",
"attempts": 0
},
{
"id": "task-67",
"status": "pending",
"attempts": 0
},
{
"id": "task-68",
"status": "pending",
"attempts": 0
},
{
"id": "task-69",
"status": "pending",
"attempts": 0
},
{
"id": "task-70",
"status": "pending",
"attempts": 0
},
{
"id": "task-71",
"status": "pending",
"attempts": 0
},
{
"id": "task-72",
"status": "pending",
"attempts": 0
},
{
"id": "task-73",
"status": "pending",
"attempts": 0
},
{
"id": "task-74",
"status": "pending",
"attempts": 0
},
{
"id": "task-75",
"status": "pending",
"attempts": 0
},
{
"id": "task-76",
"status": "pending",
"attempts": 0
},
{
"id": "task-77",
"status": "pending",
"attempts": 0
},
{
"id": "task-78",
"status": "pending",
"attempts": 0
},
{
"id": "task-79",
"status": "pending",
"attempts": 0
},
{
"id": "task-80",
"status": "pending",
"attempts": 0
},
{
"id": "task-81",
"status": "pending",
"attempts": 0
},
{
"id": "task-82",
"status": "pending",
"attempts": 0
},
{
"id": "task-83",
"status": "pending",
"attempts": 0
},
{
"id": "task-84",
"status": "pending",
"attempts": 0
},
{
"id": "task-85",
"status": "pending",
"attempts": 0
},
{
"id": "task-86",
"status": "pending",
"attempts": 0
},
{
"id": "task-87",
"status": "pending",
"attempts": 0
},
{
"id": "task-88",
"status": "pending",
"attempts": 0
},
{
"id": "task-89",
"status": "pending",
"attempts": 0
},
{
"id": "task-90",
"status": "pending",
"attempts": 0
},
{
"id": "task-91",
"status": "pending",
"attempts": 0
},
{
"id": "task-92",
"status": "pending",
"attempts": 0
},
{
"id": "task-93",
"status": "pending",
"attempts": 0
},
{
"id": "task-94",
"status": "pending",
"attempts": 0
},
{
"id": "task-95",
"status": "pending",
"attempts": 0
},
{
"id": "task-96",
"status": "pending",
"attempts": 0
},
{
"id": "task-97",
"status": "pending",
"attempts": 0
},
{
"id": "task-98",
"status": "pending",
"attempts": 0
},
{
"id": "task-99",
"status": "pending",
"attempts": 0
},
{
"id": "task-100",
"status": "pending",
"attempts": 0
},
{
"id": "task-101",
"status": "pending",
"attempts": 0
},
{
"id": "task-102",
"status": "pending",
"attempts": 0
}
],
"pmLastInstruction": "[task-1] L1(compileJava) + L6(bootJar) 통과. done_when 조건 충족.\n[task-3] ApiResponse.java + GlobalExceptionHandler.java 이미 생성됨. compileJava 통과로 done_when(build pass) 충족.\n[task-4] SecurityConfig, JwtTokenProvider, JwtAuthenticationFilter 이미 생성됨. compileJava 통과. 패키지 경로가 plan과 다르나(security/ 패키지) 빌드 통과로 done_when 충족.\n[task-5] CommonMapper.java, CommonMapper.xml, CommonService.java 이미 생성됨. compileJava 통과로 done_when(build pass) 충족.\n[task-2] DecimalUtils.java와 DecimalUtilsTest.java가 이미 존재한다. 새로 만들지 말고 기존 파일을 확인하라. verify 명령어(./gradlew test --tests '*DecimalUtils*')를 실행하여 유닛 테스트 통과를 확인하라. 테스트 실패 시에만 코드를 수정하라. 메서드 목록: parse(String), multiply(String,String), addTax(String,String), divide(String,String,int scale), sum(List<String>). 테스트 케이스: parse(null)→ZERO, multiply('100','0.1')→10.0000, 0.1+0.2=0.3.",
"pmLastSummary": "Round 1에서 backend 에이전트가 task-1(Spring Boot 프로젝트 초기화)을 성공적으로 완료했다. L1(compileJava)과 L6(bootJar) 모두 통과. 에이전트가 task-1 범위를 넘어서 task-2~5에 해당하는 파일들(DecimalUtils+Test, ApiResponse, GlobalExceptionHandler, Se",
"metrics": {
"calls": [
{
"agent": "backend",
"round": 1,
"durationMs": 236749,
"outputLength": 1074,
"timestamp": "2026-03-27T02:21:50.087Z",
"inputTokens": 941,
"outputTokens": 13038,
"cacheReadTokens": 1200300,
"cacheWriteTokens": 40613
},
{
"agent": "pm",
"round": 1,
"durationMs": 116561,
"outputLength": 2461,
"timestamp": "2026-03-27T02:23:46.852Z"
}
],
"totalDurationMs": 353310,
"totalCalls": 2,
"totalInputTokens": 941,
"totalOutputTokens": 13038,
"totalCacheReadTokens": 1200300,
"totalCacheWriteTokens": 40613
},
"savedAt": "2026-03-27T02:23:46.855Z",
"planFilePath": "/Users/gbpark/agent-pipeline/test-vex/plans/spring-migration.md",
"claudeSessionIds": {
"backend": "5ee7c47f-9138-4c93-8341-65be24073658"
}
}
@@ -0,0 +1,55 @@
# PM 판단 - Round 1
- 시간: 2026. 3. 27. 오전 11:23:46
## 분석
Round 1에서 backend 에이전트가 task-1(Spring Boot 프로젝트 초기화)을 성공적으로 완료했다. L1(compileJava)과 L6(bootJar) 모두 통과. 에이전트가 task-1 범위를 넘어서 task-2~5에 해당하는 파일들(DecimalUtils+Test, ApiResponse, GlobalExceptionHandler, SecurityConfig, JwtTokenProvider, JwtAuthenticationFilter, CommonMapper, CommonService)도 함께 생성한 점이 우수하다. compileJava와 bootJar 모두 통과했으므로 task-3(ApiResponse+GlobalExceptionHandler build pass), task-4(SecurityConfig+JwtTokenProvider+JwtAuthenticationFilter build pass), task-5(CommonMapper+CommonService build pass)의 done_when 조건이 충족되었다. 단, task-4의 파일 경로가 plan과 다르다: plan은 config/SecurityConfig.java, config/JwtTokenProvider.java, filter/JwtAuthenticationFilter.java를 지정했으나 에이전트는 모두 security/ 패키지에 배치했다. 기능상 문제없이 빌드 통과했으나 향후 태스크에서 import 경로 참조 시 주의 필요. task-2의 done_when은 'DecimalUtils all unit tests pass'인데 bootJar만 검증되었고 구체적 유닛 테스트 실행은 확인되지 않았다. 파일은 이미 존재하므로 다음 라운드에서 유닛 테스트 검증만 필요.
## 태스크 지시
- **task-1** [complete]: L1(compileJava) + L6(bootJar) 통과. done_when 조건 충족.
- **task-3** [complete]: ApiResponse.java + GlobalExceptionHandler.java 이미 생성됨. compileJava 통과로 done_when(build pass) 충족.
- **task-4** [complete]: SecurityConfig, JwtTokenProvider, JwtAuthenticationFilter 이미 생성됨. compileJava 통과. 패키지 경로가 plan과 다르나(security/ 패키지) 빌드 통과로 done_when 충족.
- **task-5** [complete]: CommonMapper.java, CommonMapper.xml, CommonService.java 이미 생성됨. compileJava 통과로 done_when(build pass) 충족.
- **task-2** [retry]: DecimalUtils.java와 DecimalUtilsTest.java가 이미 존재한다. 새로 만들지 말고 기존 파일을 확인하라. verify 명령어(./gradlew test --tests '*DecimalUtils*')를 실행하여 유닛 테스트 통과를 확인하라. 테스트 실패 시에만 코드를 수정하라. 메서드 목록: parse(String), multiply(String,String), addTax(String,String), divide(String,String,int scale), sum(List<String>). 테스트 케이스: parse(null)→ZERO, multiply('100','0.1')→10.0000, 0.1+0.2=0.3.
## 판정: **continue**
## 다음 라운드 핵심: task-2 유닛 테스트 검증 완료 후, task-6(Auth Login API, depends: task-3,4,5 완료)을 즉시 진행. 동시에 task-5에만 의존하는 task-7~task-100 중 max_concurrent=3에 따라 병렬 배치 시작. 우선순위: task-6(Auth 필수), task-7(Admin), task-8(Role) 또는 task-91(Data Generic CRUD) 등 다른 태스크의 기반이 되는 핵심 API부터.
---
## PM 원본 응답
```
```json
{
"analysis": "Round 1에서 backend 에이전트가 task-1(Spring Boot 프로젝트 초기화)을 성공적으로 완료했다. L1(compileJava)과 L6(bootJar) 모두 통과. 에이전트가 task-1 범위를 넘어서 task-2~5에 해당하는 파일들(DecimalUtils+Test, ApiResponse, GlobalExceptionHandler, SecurityConfig, JwtTokenProvider, JwtAuthenticationFilter, CommonMapper, CommonService)도 함께 생성한 점이 우수하다. compileJava와 bootJar 모두 통과했으므로 task-3(ApiResponse+GlobalExceptionHandler build pass), task-4(SecurityConfig+JwtTokenProvider+JwtAuthenticationFilter build pass), task-5(CommonMapper+CommonService build pass)의 done_when 조건이 충족되었다. 단, task-4의 파일 경로가 plan과 다르다: plan은 config/SecurityConfig.java, config/JwtTokenProvider.java, filter/JwtAuthenticationFilter.java를 지정했으나 에이전트는 모두 security/ 패키지에 배치했다. 기능상 문제없이 빌드 통과했으나 향후 태스크에서 import 경로 참조 시 주의 필요. task-2의 done_when은 'DecimalUtils all unit tests pass'인데 bootJar만 검증되었고 구체적 유닛 테스트 실행은 확인되지 않았다. 파일은 이미 존재하므로 다음 라운드에서 유닛 테스트 검증만 필요.",
"taskUpdates": [
{
"taskId": "task-1",
"status": "complete",
"instruction": "L1(compileJava) + L6(bootJar) 통과. done_when 조건 충족."
},
{
"taskId": "task-3",
"status": "complete",
"instruction": "ApiResponse.java + GlobalExceptionHandler.java 이미 생성됨. compileJava 통과로 done_when(build pass) 충족."
},
{
"taskId": "task-4",
"status": "complete",
"instruction": "SecurityConfig, JwtTokenProvider, JwtAuthenticationFilter 이미 생성됨. compileJava 통과. 패키지 경로가 plan과 다르나(security/ 패키지) 빌드 통과로 done_when 충족."
},
{
"taskId": "task-5",
"status": "complete",
"instruction": "CommonMapper.java, CommonMapper.xml, CommonService.java 이미 생성됨. compileJava 통과로 done_when(build pass) 충족."
},
{
"taskId": "task-2",
"status": "retry",
"instruction": "DecimalUtils.java와 DecimalUtilsTest.java가 이미 존재한다. 새로 만들지 말고 기존 파일을 확인하라. verify 명령어(./gradlew test --tests '*DecimalUtils*')를 실행하여 유닛 테스트 통과를 확인하라. 테스트 실패 시에만 코드를 수정하라. 메서드 목록: parse(String), multiply(String,String), addTax(String,String), divide(String,String,int scale), sum(List<String>). 테스트 케이스: parse(null)→ZERO, multiply('100','0.1')→10.0000, 0.1+0.2=0.3."
}
],
"newTasks": [],
"overallStatus": "continue",
"nextRoundFocus": "task-2 유닛 테스트 검증 완료 후, task-6(Auth Login API, depends: task-3,4,5 완료)을 즉시 진행. 동시에 task-5에만 의존하는 task-7~task-100 중 max_concurrent=3에 따라 병렬 배치 시작. 우선순위: task-6(Auth 필수), task-7(Admin), task-8(Role) 또는 task-91(Data Generic CRUD) 등 다른 태스크의 기반이 되는 핵심 API부터."
}
```
```
@@ -0,0 +1,80 @@
# Round 1
- 시간: 2026. 3. 27. 오전 11:21:50
## backend (성공)
### 에이전트 응답
```
`./gradlew bootJar` 빌드 성공했습니다.
## 완료 내역
생성된 파일:
**Gradle 빌드 파일**
- `settings.gradle` — 프로젝트명 `erp-backend`
- `build.gradle` — Spring Boot 3.3.5, Java 21, MyBatis, jjwt, PostgreSQL 의존성
- `gradle/wrapper/` — Gradle 8.10 wrapper
**Java 소스**
| 파일 | 역할 |
|------|------|
| `ErpApplication.java` | `@SpringBootApplication` + `@MapperScan` |
| `dto/ApiResponse.java` | 공통 응답 DTO (`success`, `data`, `message`) |
| `util/DecimalUtils.java` | BigDecimal 유틸 (money 계산) |
| `security/JwtTokenProvider.java` | JWT 생성/검증 (jjwt 0.12.3) |
| `security/JwtAuthenticationFilter.java` | Bearer 토큰 파싱 → `companyCode` attribute 주입 |
| `security/SecurityConfig.java` | `/api/auth/**` 공개, 나머지 인증 필요 |
| `config/GlobalExceptionHandler.java` | 전역 예외 처리 |
| `mapper/CommonMapper.java` | 공통 Mapper 인터페이스 |
| `service/CommonService.java` | `applyCompanyCodeFilter`, `applyPagination` |
**리소스**
- `application.yml` — port 8081, PostgreSQL, MyBatis, JWT 설정
- `mapper/CommonMapper.xml` — `companyCodeFilter`, `pagination` SQL fragment 정의
**빌드 결과**: `build/libs/erp-backend-0.0.1-SNAPSHOT.jar` (27MB)
```
## 코드 변경 (git diff)
### 새 파일
```
_pipeline/knowledge/incident-history.json
_pipeline/mailbox/mcp-backend.json
_pipeline/mailbox/mcp-frontend.json
_pipeline/pipeline-state.json
_pipeline/runs/2026-03-27_pipe-20260327014616-cori/rounds/round-2-pm.md
_pipeline/runs/2026-03-27_pipe-20260327014616-cori/rounds/round-2.md
_pipeline/runs/2026-03-27_pipe-20260327021752-7ctg/heartbeat.json
_pipeline/runs/2026-03-27_pipe-20260327021752-7ctg/plan.md
_pipeline/runs/2026-03-27_pipe-20260327021752-7ctg/progress.md
backend-spring/build.gradle
backend-spring/gradle/wrapper/gradle-wrapper.jar
backend-spring/gradle/wrapper/gradle-wrapper.properties
backend-spring/gradlew
backend-spring/gradlew.bat
backend-spring/settings.gradle
backend-spring/src/main/java/com/erp/ErpApplication.java
backend-spring/src/main/java/com/erp/config/GlobalExceptionHandler.java
backend-spring/src/main/java/com/erp/dto/ApiResponse.java
backend-spring/src/main/java/com/erp/mapper/CommonMapper.java
backend-spring/src/main/java/com/erp/security/JwtAuthenticationFilter.java
backend-spring/src/main/java/com/erp/security/JwtTokenProvider.java
backend-spring/src/main/java/com/erp/security/SecurityConfig.java
backend-spring/src/main/java/com/erp/service/CommonService.java
backend-spring/src/main/java/com/erp/util/DecimalUtils.java
backend-spring/src/main/resources/application.yml
backend-spring/src/main/resources/mapper/CommonMapper.xml
backend-spring/src/test/java/com/erp/util/DecimalUtilsTest.java
```
## 검증 결과
### L1 [PASS]: L1 통과: cd backend-spring && ./gradlew compileJava 2>&1 | tail -20
```
L1 통과: cd backend-spring && ./gradlew compileJava 2>&1 | tail -20
```
### L6 [PASS]: L6 통과: cd backend-spring && ./gradlew bootJar 2>&1 | tail -20
```
L6 통과: cd backend-spring && ./gradlew bootJar 2>&1 | tail -20
```
+2 -1
View File
@@ -24,12 +24,13 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
runtimeOnly 'org.postgresql:postgresql'
implementation 'org.postgresql:postgresql'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
@@ -0,0 +1,428 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.AdminService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/admin")
@RequiredArgsConstructor
@Slf4j
public class AdminController {
private final AdminService adminService;
// ── 메뉴 관리 ──────────────────────────────────────────────────────────
/**
* GET /api/admin/menus
* 관리자 메뉴 목록 조회
*/
@GetMapping("/menus")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAdminMenus(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestAttribute("userId") String userId,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
params.put("userType", role);
params.put("userId", userId);
params.putIfAbsent("userLang", "ko");
params.put("isManagementScreen",
params.get("menuType") == null || "true".equals(params.get("includeInactive")));
return ResponseEntity.ok(ApiResponse.success(adminService.getAdminMenuList(params), "관리자 메뉴 목록 조회 성공"));
}
/**
* GET /api/admin/user-menus
* 사용자 메뉴 목록 조회 (사이드바)
*/
@GetMapping("/user-menus")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getUserMenus(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestAttribute("userId") String userId,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
params.put("userType", role);
params.put("userId", userId);
params.putIfAbsent("userLang", "ko");
return ResponseEntity.ok(ApiResponse.success(adminService.getUserMenuList(params), "사용자 메뉴 목록 조회 성공"));
}
/**
* GET /api/admin/pop-menus
* POP 메뉴 목록 조회
*/
@GetMapping("/pop-menus")
public ResponseEntity<ApiResponse<Map<String, Object>>> getPopMenus(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("userType", role);
return ResponseEntity.ok(ApiResponse.success(adminService.getPopMenuList(params), "POP 메뉴 목록 조회 성공"));
}
/**
* GET /api/admin/menus/{menuId}
* 메뉴 단건 조회
*/
@GetMapping("/menus/{menuId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMenuInfo(@PathVariable String menuId) {
Map<String, Object> menu = adminService.getMenuInfo(menuId);
if (menu == null) {
return ResponseEntity.status(404).body(ApiResponse.error("메뉴를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(menu, "메뉴 정보 조회 성공"));
}
/**
* POST /api/admin/menus
* 메뉴 등록
*/
@PostMapping("/menus")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveMenu(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(adminService.saveMenu(body), "메뉴 등록 성공"));
}
/**
* PUT /api/admin/menus/{menuId}
* 메뉴 수정
*/
@PutMapping("/menus/{menuId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateMenu(
@PathVariable String menuId,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(adminService.updateMenu(menuId, body), "메뉴 수정 성공"));
}
/**
* DELETE /api/admin/menus/{menuId}
* 메뉴 삭제
*/
@DeleteMapping("/menus/{menuId}")
public ResponseEntity<ApiResponse<Void>> deleteMenu(@PathVariable String menuId) {
adminService.deleteMenu(menuId);
return ResponseEntity.ok(ApiResponse.success(null, "메뉴 삭제 성공"));
}
/**
* PUT /api/admin/menus/{menuId}/toggle
* 메뉴 상태 토글
*/
@PutMapping("/menus/{menuId}/toggle")
public ResponseEntity<ApiResponse<Map<String, Object>>> toggleMenuStatus(@PathVariable String menuId) {
return ResponseEntity.ok(ApiResponse.success(adminService.toggleMenuStatus(menuId), "메뉴 상태 변경 성공"));
}
/**
* DELETE /api/admin/menus/batch
* 메뉴 일괄 삭제
*/
@DeleteMapping("/menus/batch")
public ResponseEntity<ApiResponse<Void>> deleteMenusBatch(@RequestBody Map<String, Object> body) {
@SuppressWarnings("unchecked")
List<String> menuIds = (List<String>) body.get("menuIds");
if (menuIds != null) {
menuIds.forEach(adminService::deleteMenu);
}
return ResponseEntity.ok(ApiResponse.success(null, "메뉴 일괄 삭제 성공"));
}
// ── 사용자 관리 ────────────────────────────────────────────────────────
/**
* GET /api/admin/users
* 사용자 목록 조회
*/
@GetMapping("/users")
public ResponseEntity<?> getUserList(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestParam Map<String, Object> params) {
// SUPER_ADMIN이 아닌 경우 자사 필터 적용
if (!"SUPER_ADMIN".equals(role)) {
params.put("companyCode", companyCode);
} else {
params.put("companyCode", "*");
}
params.putIfAbsent("page", "1");
params.putIfAbsent("limit", "20");
Map<String, Object> result = adminService.getUserList(params);
// Node.js 응답 형식: success, data, total, searchType, pagination, message
Map<String, Object> response = new HashMap<>(result);
response.put("success", true);
response.put("message", "사용자 목록 조회 성공");
return ResponseEntity.ok(response);
}
/**
* GET /api/admin/users/{userId}
* 사용자 단건 조회
*/
@GetMapping("/users/{userId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getUserInfo(@PathVariable String userId) {
Map<String, Object> user = adminService.getUserInfo(userId);
if (user == null) {
return ResponseEntity.status(404).body(ApiResponse.error("사용자를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(user, "사용자 정보 조회 성공"));
}
/**
* GET /api/admin/users/{userId}/history
* 사용자 변경이력 조회
*/
@GetMapping("/users/{userId}/history")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getUserHistory(
@PathVariable String userId,
@RequestParam Map<String, Object> params) {
return ResponseEntity.ok(ApiResponse.success(adminService.getUserHistory(userId, params), "사용자 변경이력 조회 성공"));
}
/**
* GET /api/admin/users/{userId}/with-dept
* 사원 + 부서 정보 조회 (수정 모달용)
*/
@GetMapping("/users/{userId}/with-dept")
public ResponseEntity<ApiResponse<Map<String, Object>>> getUserWithDept(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String userId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("userId", userId);
Map<String, Object> result = adminService.getUserWithDept(params);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("사용자를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
/**
* POST /api/admin/users/with-dept
* 사원 + 부서 통합 저장
*/
@PostMapping("/users/with-dept")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveUserWithDept(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(adminService.saveUserWithDept(body)));
}
/**
* POST /api/admin/users
* 사용자 등록/수정
*/
@PostMapping("/users")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveUser(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(adminService.saveUser(body), "사용자 저장 성공"));
}
/**
* PUT /api/admin/users/{userId}
* 사용자 수정 (REST)
*/
@PutMapping("/users/{userId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateUser(
@PathVariable String userId,
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("userId", userId);
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(adminService.saveUser(body), "사용자 수정 성공"));
}
/**
* PATCH /api/admin/users/{userId}/status
* 사용자 상태 변경
*/
@PatchMapping("/users/{userId}/status")
public ResponseEntity<ApiResponse<Void>> changeUserStatus(
@PathVariable String userId,
@RequestBody Map<String, Object> body) {
String status = (String) body.get("status");
adminService.changeUserStatus(userId, status);
return ResponseEntity.ok(ApiResponse.success(null, "사용자 상태 변경 성공"));
}
/**
* POST /api/admin/users/reset-password
* 비밀번호 초기화
*/
@PostMapping("/users/reset-password")
public ResponseEntity<ApiResponse<Void>> resetUserPassword(@RequestBody Map<String, Object> body) {
String userId = (String) body.get("userId");
adminService.resetUserPassword(userId);
return ResponseEntity.ok(ApiResponse.success(null, "비밀번호 초기화 성공"));
}
/**
* POST /api/admin/users/check-duplicate
* 사용자 ID 중복 확인
*/
@PostMapping("/users/check-duplicate")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkDuplicateUserId(
@RequestBody Map<String, Object> body) {
String userId = (String) body.get("userId");
Map<String, Object> existing = adminService.getUserInfo(userId);
Map<String, Object> result = new HashMap<>();
result.put("isDuplicate", existing != null);
return ResponseEntity.ok(ApiResponse.success(result, "아이디 중복 확인 완료"));
}
// ── 프로필 수정 ────────────────────────────────────────────────────────
/**
* PUT /api/admin/profile
* 프로필 수정 (본인)
*/
@PutMapping("/profile")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateProfile(
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
body.put("userId", userId);
return ResponseEntity.ok(ApiResponse.success(adminService.saveUser(body), "프로필 수정 성공"));
}
// ── 부서 관리 ──────────────────────────────────────────────────────────
/**
* GET /api/admin/departments
* 부서 목록 조회
*/
@GetMapping("/departments")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDepartmentList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(adminService.getDepartmentList(params), "부서 목록 조회 성공"));
}
// ── 회사 관리 ──────────────────────────────────────────────────────────
/**
* GET /api/admin/companies
* 회사 목록 조회
*/
@GetMapping("/companies")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCompanyList() {
return ResponseEntity.ok(ApiResponse.success(adminService.getCompanyList(), "회사 목록 조회 성공"));
}
/**
* GET /api/admin/companies/db
* 실제 DB에서 회사 목록 조회
*/
@GetMapping("/companies/db")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCompanyListFromDB() {
return ResponseEntity.ok(ApiResponse.success(adminService.getCompanyList(), "회사 목록 조회 성공"));
}
/**
* GET /api/admin/companies/{companyCode}
* 회사 단건 조회
*/
@GetMapping("/companies/{companyCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCompanyByCode(
@PathVariable String companyCode) {
Map<String, Object> company = adminService.getCompanyByCode(companyCode);
if (company == null) {
return ResponseEntity.status(404).body(ApiResponse.error("회사를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(company, "회사 조회 성공"));
}
/**
* POST /api/admin/companies
* 회사 등록
*/
@PostMapping("/companies")
public ResponseEntity<ApiResponse<Map<String, Object>>> createCompany(
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(adminService.createCompany(body), "회사 등록 성공"));
}
/**
* PUT /api/admin/companies/{companyCode}
* 회사 수정
*/
@PutMapping("/companies/{companyCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateCompany(
@PathVariable String companyCode,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(adminService.updateCompanyInfo(companyCode, body), "회사 수정 성공"));
}
/**
* DELETE /api/admin/companies/{companyCode}
* 회사 삭제 (비활성화)
*/
@DeleteMapping("/companies/{companyCode}")
public ResponseEntity<ApiResponse<Void>> deleteCompany(@PathVariable String companyCode) {
adminService.deleteCompany(companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "회사 삭제 성공"));
}
// ── 로케일 관리 ────────────────────────────────────────────────────────
/**
* GET /api/admin/user-locale
* 사용자 로케일 조회
*/
@GetMapping("/user-locale")
public ResponseEntity<ApiResponse<Object>> getUserLocale(
@RequestAttribute("userId") String userId) {
Map<String, Object> row = adminService.getUserLocale(userId);
Object locale = row.getOrDefault("locale", "KR");
return ResponseEntity.ok(ApiResponse.success(locale, "사용자 로케일 조회 성공"));
}
/**
* POST /api/admin/user-locale
* 사용자 로케일 설정
*/
@PostMapping("/user-locale")
public ResponseEntity<ApiResponse<Object>> setUserLocale(
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
String locale = (String) body.get("locale");
if (locale == null || locale.isBlank()) {
return ResponseEntity.badRequest().body(ApiResponse.error("로케일이 필요합니다."));
}
try {
adminService.setUserLocale(userId, locale);
return ResponseEntity.ok(ApiResponse.success(locale, "사용자 로케일 설정 성공"));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
}
}
// ── 테이블 스키마 ──────────────────────────────────────────────────────
/**
* GET /api/admin/tables/{tableName}/schema
* 테이블 스키마 조회
*/
@GetMapping("/tables/{tableName}/schema")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTableSchema(
@PathVariable String tableName) {
return ResponseEntity.ok(ApiResponse.success(adminService.getTableSchema(tableName), "테이블 스키마 조회 성공"));
}
}
@@ -0,0 +1,44 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.AiAssistantProxyService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequiredArgsConstructor
@Slf4j
public class AiAssistantProxyController {
private final AiAssistantProxyService aiAssistantProxyService;
@GetMapping("/api/ai/v1/status")
public ResponseEntity<ApiResponse<Map<String, Object>>> getStatus() {
Map<String, Object> result = new HashMap<>();
result.put("serviceUrl", "http://127.0.0.1:3100");
result.put("status", "ok");
result.put("message", "AI 어시스턴트 프록시 서비스가 구성되었습니다.");
return ResponseEntity.ok(ApiResponse.success(result));
}
@RequestMapping(
value = "/api/ai/v1/**",
method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT,
RequestMethod.DELETE, RequestMethod.PATCH}
)
public ResponseEntity<byte[]> proxy(
HttpServletRequest request,
HttpHeaders headers,
@RequestBody(required = false) byte[] body) {
String path = request.getRequestURI().replaceFirst("^/api/ai/v1", "");
String query = request.getQueryString();
HttpMethod method = HttpMethod.valueOf(request.getMethod());
return aiAssistantProxyService.forward(path, query, method, headers, body);
}
}
@@ -0,0 +1,74 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.AnalyticsReportService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/analytics-report")
@RequiredArgsConstructor
@Slf4j
public class AnalyticsReportController {
private final AnalyticsReportService analyticsReportService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getAnalyticsReportList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(analyticsReportService.getAnalyticsReportList(params)));
}
@GetMapping("/production/summary")
public ResponseEntity<ApiResponse<Map<String, Object>>> getProductionSummary(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(analyticsReportService.getProductionSummary(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getAnalyticsReportInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(analyticsReportService.getAnalyticsReportInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertAnalyticsReport(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(analyticsReportService.insertAnalyticsReport(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateAnalyticsReport(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(analyticsReportService.updateAnalyticsReport(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteAnalyticsReport(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(analyticsReportService.deleteAnalyticsReport(params)));
}
}
@@ -0,0 +1,388 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ApprovalService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/approvals")
@RequiredArgsConstructor
@Slf4j
public class ApprovalController {
private final ApprovalService approvalService;
// ═══════════════════════════════════════════════════════════════
// approval_definitions
// ═══════════════════════════════════════════════════════════════
@GetMapping("/definitions")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDefinitions(
@RequestParam Map<String, Object> params,
@RequestAttribute("companyCode") String companyCode) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(approvalService.getDefinitions(params)));
}
@GetMapping("/definitions/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDefinition(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("definitionId", id);
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(approvalService.getDefinition(params)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
@PostMapping("/definitions")
public ResponseEntity<ApiResponse<Map<String, Object>>> createDefinition(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
if (body.get("definition_name") == null)
return ResponseEntity.status(400).body(ApiResponse.error("결재 유형명은 필수입니다."));
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(approvalService.createDefinition(body), "결재 유형이 생성되었습니다."));
} catch (Exception e) {
log.error("결재 유형 생성 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("결재 유형 생성 중 오류가 발생했습니다."));
}
}
@PutMapping("/definitions/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateDefinition(
@PathVariable Long id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("definitionId", id);
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.ok(ApiResponse.success(approvalService.updateDefinition(body), "결재 유형이 수정되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("결재 유형 수정 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("결재 유형 수정 중 오류가 발생했습니다."));
}
}
@DeleteMapping("/definitions/{id}")
public ResponseEntity<ApiResponse<Void>> deleteDefinition(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("definitionId", id);
params.put("companyCode", companyCode);
try {
approvalService.deleteDefinition(params);
return ResponseEntity.ok(ApiResponse.success(null, "결재 유형이 삭제되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("결재 유형 삭제 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("결재 유형 삭제 중 오류가 발생했습니다."));
}
}
// ═══════════════════════════════════════════════════════════════
// approval_line_templates
// ═══════════════════════════════════════════════════════════════
@GetMapping("/templates")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTemplates(
@RequestParam Map<String, Object> params,
@RequestAttribute("companyCode") String companyCode) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(approvalService.getTemplates(params)));
}
@GetMapping("/templates/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTemplate(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("templateId", id);
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(approvalService.getTemplate(params)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
@PostMapping("/templates")
public ResponseEntity<ApiResponse<Map<String, Object>>> createTemplate(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
if (body.get("template_name") == null)
return ResponseEntity.status(400).body(ApiResponse.error("템플릿명은 필수입니다."));
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(approvalService.createTemplate(body), "결재선 템플릿이 생성되었습니다."));
} catch (Exception e) {
log.error("템플릿 생성 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("결재선 템플릿 생성 중 오류가 발생했습니다."));
}
}
@PutMapping("/templates/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateTemplate(
@PathVariable Long id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("templateId", id);
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.ok(ApiResponse.success(approvalService.updateTemplate(body), "결재선 템플릿이 수정되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("템플릿 수정 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("결재선 템플릿 수정 중 오류가 발생했습니다."));
}
}
@DeleteMapping("/templates/{id}")
public ResponseEntity<ApiResponse<Void>> deleteTemplate(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("templateId", id);
params.put("companyCode", companyCode);
try {
approvalService.deleteTemplate(params);
return ResponseEntity.ok(ApiResponse.success(null, "결재선 템플릿이 삭제되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("템플릿 삭제 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("결재선 템플릿 삭제 중 오류가 발생했습니다."));
}
}
// ═══════════════════════════════════════════════════════════════
// approval_requests
// ═══════════════════════════════════════════════════════════════
@GetMapping("/requests")
public ResponseEntity<ApiResponse<Map<String, Object>>> getRequests(
@RequestParam Map<String, Object> params,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
params.put("companyCode", companyCode);
params.put("userId", userId);
return ResponseEntity.ok(ApiResponse.success(approvalService.getRequests(params)));
}
@GetMapping("/requests/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getRequest(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("requestId", id);
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(approvalService.getRequest(params)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
@PostMapping("/requests")
public ResponseEntity<ApiResponse<Map<String, Object>>> createRequest(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(approvalService.createRequest(body), "결재 요청이 생성되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("결재 요청 생성 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("결재 요청 생성 중 오류가 발생했습니다."));
}
}
@PostMapping("/requests/{id}/cancel")
public ResponseEntity<ApiResponse<Void>> cancelRequest(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
Map<String, Object> params = new HashMap<>();
params.put("requestId", id);
params.put("companyCode", companyCode);
params.put("userId", userId);
try {
approvalService.cancelRequest(params);
return ResponseEntity.ok(ApiResponse.success(null, "결재 요청이 회수되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("결재 요청 회수 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("결재 요청 회수 중 오류가 발생했습니다."));
}
}
@PostMapping("/requests/{id}/post-approve")
public ResponseEntity<ApiResponse<Void>> postApprove(
@PathVariable Long id,
@RequestBody(required = false) Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
Map<String, Object> params = new HashMap<>();
if (body != null) params.putAll(body);
params.put("requestId", id);
params.put("companyCode", companyCode);
params.put("userId", userId);
try {
approvalService.postApprove(params);
return ResponseEntity.ok(ApiResponse.success(null, "후결 처리가 완료되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("후결 처리 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("후결 처리 중 오류가 발생했습니다."));
}
}
// ═══════════════════════════════════════════════════════════════
// approval_lines
// ═══════════════════════════════════════════════════════════════
@GetMapping("/my-pending")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMyPendingLines(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("userId", userId);
return ResponseEntity.ok(ApiResponse.success(approvalService.getMyPendingLines(params)));
}
@PostMapping("/lines/{lineId}/process")
public ResponseEntity<ApiResponse<Void>> processApproval(
@PathVariable Long lineId,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
String action = (String) body.get("action");
if (!List.of("approved", "rejected").contains(action))
return ResponseEntity.status(400).body(ApiResponse.error("액션은 approved 또는 rejected여야 합니다."));
Map<String, Object> params = new HashMap<>(body);
params.put("lineId", lineId);
params.put("companyCode", companyCode);
params.put("userId", userId);
try {
approvalService.processApproval(params);
String msg = "approved".equals(action) ? "승인 처리되었습니다." : "반려 처리되었습니다.";
return ResponseEntity.ok(ApiResponse.success(null, msg));
} catch (IllegalArgumentException e) {
int status = e.getMessage().contains("찾을 수 없") ? 404 : 400;
if (e.getMessage().contains("결재자로 지정")) status = 403;
return ResponseEntity.status(status).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("결재 처리 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("결재 처리 중 오류가 발생했습니다."));
}
}
// ═══════════════════════════════════════════════════════════════
// approval_proxy_settings
// ═══════════════════════════════════════════════════════════════
// /proxy-settings/check/:userId 가 /{id} 보다 먼저 매핑되어야 하므로 먼저 선언
@GetMapping("/proxy-settings/check/{targetUserId}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> checkActiveProxy(
@PathVariable String targetUserId,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("userId", targetUserId);
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(approvalService.checkActiveProxy(params)));
}
@GetMapping("/proxy-settings")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getProxySettings(
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(approvalService.getProxySettings(params)));
}
@PostMapping("/proxy-settings")
public ResponseEntity<ApiResponse<Map<String, Object>>> createProxySetting(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
body.put("companyCode", companyCode);
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(approvalService.createProxySetting(body), "대결 위임이 생성되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("대결 위임 생성 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("대결 위임 생성 중 오류가 발생했습니다."));
}
}
@PutMapping("/proxy-settings/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateProxySetting(
@PathVariable Long id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
body.put("id", id);
body.put("companyCode", companyCode);
try {
return ResponseEntity.ok(ApiResponse.success(approvalService.updateProxySetting(body), "대결 위임이 수정되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("대결 위임 수정 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("대결 위임 수정 중 오류가 발생했습니다."));
}
}
@DeleteMapping("/proxy-settings/{id}")
public ResponseEntity<ApiResponse<Void>> deleteProxySetting(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
try {
approvalService.deleteProxySetting(params);
return ResponseEntity.ok(ApiResponse.success(null, "대결 위임이 삭제되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("대결 위임 삭제 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("대결 위임 삭제 중 오류가 발생했습니다."));
}
}
}
@@ -0,0 +1,156 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.AuditLogService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/audit-logs")
@RequiredArgsConstructor
@Slf4j
public class AuditLogController {
private final AuditLogService auditLogService;
/**
* GET /api/audit-logs
* 감사 로그 목록 조회 (페이지네이션 + 필터)
* 응답: { success, data, total, page, limit }
*/
@GetMapping
public ResponseEntity<Map<String, Object>> getAuditLogs(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestParam Map<String, Object> queryParams) {
boolean isSuperAdmin = isSuperAdmin(role);
// SUPER_ADMIN: 쿼리 파라미터 companyCode 사용(없으면 전체); 일반: JWT companyCode 강제 적용
String filterCompany = isSuperAdmin
? (String) queryParams.get("companyCode")
: companyCode;
Map<String, Object> filters = new HashMap<>(queryParams);
filters.put("filterCompany", filterCompany);
int page = toInt(queryParams.get("page"), 1);
int limit = toInt(queryParams.get("limit"), 50);
filters.put("page", page);
filters.put("limit", limit);
Map<String, Object> result = auditLogService.queryLogs(filters, isSuperAdmin);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("data", result.get("data"));
response.put("total", result.get("total"));
response.put("page", page);
response.put("limit", limit);
return ResponseEntity.ok(response);
}
/**
* GET /api/audit-logs/stats
* 감사 로그 통계 (일별 / 리소스타입 / 액션 / 상위사용자)
*/
@GetMapping("/stats")
public ResponseEntity<ApiResponse<Map<String, Object>>> getAuditLogStats(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestParam(value = "companyCode", required = false) String queryCompanyCode,
@RequestParam(required = false, defaultValue = "30") int days) {
boolean isSuperAdmin = isSuperAdmin(role);
String targetCompany = isSuperAdmin ? queryCompanyCode : companyCode;
Map<String, Object> stats = auditLogService.getStats(targetCompany, days);
return ResponseEntity.ok(ApiResponse.success(stats));
}
/**
* GET /api/audit-logs/users
* 감사 로그 사용자 목록 (로그 건수 포함)
*/
@GetMapping("/users")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAuditLogUsers(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestParam(value = "companyCode", required = false) String queryCompanyCode) {
boolean isSuperAdmin = isSuperAdmin(role);
String targetCompany = isSuperAdmin ? queryCompanyCode : companyCode;
List<Map<String, Object>> users = auditLogService.getAuditLogUsers(targetCompany, isSuperAdmin);
return ResponseEntity.ok(ApiResponse.success(users));
}
/**
* POST /api/audit-logs
* 감사 로그 직접 기록 (프론트엔드 오케스트레이션용)
*/
@PostMapping
public ResponseEntity<ApiResponse<Void>> createAuditLog(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestAttribute("role") String role,
@RequestBody Map<String, Object> body,
HttpServletRequest request) {
String action = (String) body.get("action");
String resourceType = (String) body.get("resourceType");
if (action == null || action.isBlank() || resourceType == null || resourceType.isBlank()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("action, resourceType은 필수입니다."));
}
Map<String, Object> params = new HashMap<>(body);
params.put("companyCode", companyCode);
params.put("userId", userId);
// userName: request attribute(JWT) → body fallback
if (!params.containsKey("userName")) {
Object userName = request.getAttribute("userName");
if (userName != null) {
params.put("userName", userName);
}
}
params.put("ipAddress", resolveClientIp(request));
params.put("requestPath", request.getRequestURI());
auditLogService.insertAuditLog(params);
return ResponseEntity.ok(ApiResponse.success(null));
}
// ── 내부 유틸 ────────────────────────────────────────────────────────────
private boolean isSuperAdmin(String role) {
return "SUPER_ADMIN".equals(role);
}
private int toInt(Object value, int defaultVal) {
if (value == null) return defaultVal;
try {
return Integer.parseInt(value.toString());
} catch (NumberFormatException e) {
return defaultVal;
}
}
private String resolveClientIp(HttpServletRequest request) {
String forwarded = request.getHeader("X-Forwarded-For");
if (forwarded != null && !forwarded.isBlank()) {
return forwarded.split(",")[0].trim();
}
String realIp = request.getHeader("X-Real-IP");
if (realIp != null && !realIp.isBlank()) return realIp;
return request.getRemoteAddr();
}
}
@@ -0,0 +1,184 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.security.JwtTokenProvider;
import com.erp.service.AuthService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
@Slf4j
public class AuthController {
private final AuthService authService;
private final JwtTokenProvider jwtTokenProvider;
/**
* POST /api/auth/login
*/
@PostMapping("/login")
public ResponseEntity<ApiResponse<Map<String, Object>>> login(
@RequestBody Map<String, Object> body,
HttpServletRequest request) {
String userId = (String) body.get("userId");
String password = (String) body.get("password");
if (!StringUtils.hasText(userId) || !StringUtils.hasText(password)) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("사용자 ID와 비밀번호를 입력해주세요."));
}
Map<String, Object> params = new HashMap<>(body);
params.put("remoteAddr", getRemoteAddr(request));
Map<String, Object> result = authService.login(params);
if (Boolean.TRUE.equals(result.get("loginFailed"))) {
return ResponseEntity.status(401)
.body(ApiResponse.error((String) result.get("errorReason")));
}
return ResponseEntity.ok(ApiResponse.success(result, "로그인 성공"));
}
/**
* POST /api/auth/refresh
*/
@PostMapping("/refresh")
public ResponseEntity<ApiResponse<Map<String, Object>>> refresh(HttpServletRequest request) {
String token = resolveToken(request);
if (!StringUtils.hasText(token)) {
return ResponseEntity.status(401).body(ApiResponse.error("토큰이 필요합니다."));
}
try {
Map<String, Object> data = authService.refreshToken(token);
return ResponseEntity.ok(ApiResponse.success(data, "토큰 갱신 성공"));
} catch (Exception e) {
return ResponseEntity.status(401).body(ApiResponse.error("토큰 갱신에 실패했습니다."));
}
}
/**
* GET /api/auth/me
*/
@GetMapping("/me")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCurrentUser(HttpServletRequest request) {
String token = resolveToken(request);
if (!StringUtils.hasText(token) || !jwtTokenProvider.validateToken(token)) {
return ResponseEntity.status(401).body(ApiResponse.error("인증되지 않은 사용자입니다."));
}
Map<String, Object> userInfo = authService.getUserInfo(token);
if (userInfo == null) {
return ResponseEntity.status(401).body(ApiResponse.error("사용자 정보를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(userInfo, "사용자 정보 조회 성공"));
}
/**
* GET /api/auth/status
*/
@GetMapping("/status")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkAuthStatus(HttpServletRequest request) {
String token = resolveToken(request);
Map<String, Object> status = authService.checkAuthStatus(token);
return ResponseEntity.ok(ApiResponse.success(status, "세션 상태 확인"));
}
/**
* POST /api/auth/logout
*/
@PostMapping("/logout")
public ResponseEntity<ApiResponse<Void>> logout(HttpServletRequest request) {
String token = resolveToken(request);
if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
try {
String userId = jwtTokenProvider.getUserId(token);
authService.logout(userId, getRemoteAddr(request));
} catch (Exception e) {
log.warn("로그아웃 처리 중 오류 (무시): {}", e.getMessage());
}
}
return ResponseEntity.ok(ApiResponse.success(null, "로그아웃되었습니다."));
}
/**
* POST /api/auth/switch-company
*/
@PostMapping("/switch-company")
public ResponseEntity<ApiResponse<Map<String, Object>>> switchCompany(
@RequestBody Map<String, Object> body,
HttpServletRequest request) {
String token = resolveToken(request);
if (!StringUtils.hasText(token) || !jwtTokenProvider.validateToken(token)) {
return ResponseEntity.status(401).body(ApiResponse.error("인증 토큰이 필요합니다."));
}
String companyCode = (String) body.get("companyCode");
if (!StringUtils.hasText(companyCode)) {
return ResponseEntity.badRequest().body(ApiResponse.error("전환할 회사 코드가 필요합니다."));
}
try {
Map<String, Object> result = authService.switchCompany(token, companyCode.trim());
return ResponseEntity.ok(ApiResponse.success(result, "회사 전환 완료"));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(403).body(ApiResponse.error(e.getMessage()));
}
}
/**
* POST /api/auth/signup
*/
@PostMapping("/signup")
public ResponseEntity<ApiResponse<Void>> signup(@RequestBody Map<String, Object> body) {
String userId = (String) body.get("userId");
String password = (String) body.get("password");
String userName = (String) body.get("userName");
String phoneNumber = (String) body.get("phoneNumber");
String licenseNumber = (String) body.get("licenseNumber");
String vehicleNumber = (String) body.get("vehicleNumber");
if (!StringUtils.hasText(userId) || !StringUtils.hasText(password)
|| !StringUtils.hasText(userName) || !StringUtils.hasText(phoneNumber)
|| !StringUtils.hasText(licenseNumber) || !StringUtils.hasText(vehicleNumber)) {
return ResponseEntity.badRequest().body(ApiResponse.error("필수 입력값이 누락되었습니다."));
}
try {
authService.signupDriver(new HashMap<>(body));
return ResponseEntity.status(201).body(ApiResponse.success(null, "회원가입이 완료되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
}
}
// ── 내부 유틸 ──────────────────────────────────────────────────────────
private String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
if (StringUtils.hasText(bearer) && bearer.startsWith("Bearer ")) {
return bearer.substring(7);
}
return null;
}
private String getRemoteAddr(HttpServletRequest request) {
String addr = request.getHeader("X-Forwarded-For");
if (!StringUtils.hasText(addr)) {
addr = request.getRemoteAddr();
}
return addr != null ? addr : "unknown";
}
}
@@ -0,0 +1,103 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.BarcodeLabelService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/barcode-label")
@RequiredArgsConstructor
@Slf4j
public class BarcodeLabelController {
private final BarcodeLabelService barcodeLabelService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBarcodeLabelList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(barcodeLabelService.getBarcodeLabelList(params)));
}
@GetMapping("/templates")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTemplateList() {
return ResponseEntity.ok(ApiResponse.success(barcodeLabelService.getBarcodeLabelTemplateList()));
}
@GetMapping("/templates/{templateId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTemplateInfo(
@PathVariable String templateId) {
Map<String, Object> result = barcodeLabelService.getBarcodeLabelTemplateInfo(templateId);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("템플릿을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/{labelId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBarcodeLabelInfo(
@PathVariable String labelId) {
Map<String, Object> result = barcodeLabelService.getBarcodeLabelInfo(labelId);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("라벨을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/{labelId}/layout")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBarcodeLabelLayout(
@PathVariable String labelId) {
Map<String, Object> result = barcodeLabelService.getBarcodeLabelLayout(labelId);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("레이아웃을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertBarcodeLabel(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("createdBy", userId);
Map<String, Object> result = barcodeLabelService.insertBarcodeLabel(body);
return ResponseEntity.status(201).body(ApiResponse.success(result, "라벨이 생성되었습니다."));
}
@PostMapping("/{labelId}/copy")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyBarcodeLabel(
@PathVariable String labelId,
@RequestAttribute(value = "userId", required = false) String userId) {
String newLabelId = barcodeLabelService.copyBarcodeLabel(labelId, userId);
if (newLabelId == null) return ResponseEntity.status(404).body(ApiResponse.error("복사할 라벨을 찾을 수 없습니다."));
Map<String, Object> result = new HashMap<>();
result.put("labelId", newLabelId);
return ResponseEntity.status(201).body(ApiResponse.success(result, "라벨이 복사되었습니다."));
}
@PutMapping("/{labelId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateBarcodeLabel(
@PathVariable String labelId,
@RequestBody Map<String, Object> body) {
boolean updated = barcodeLabelService.updateBarcodeLabel(labelId, body);
if (!updated) return ResponseEntity.status(404).body(ApiResponse.error("라벨을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null, "라벨이 수정되었습니다."));
}
@PutMapping("/{labelId}/layout")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveBarcodeLabelLayout(
@PathVariable String labelId,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
barcodeLabelService.saveBarcodeLabelLayout(labelId, body, userId);
return ResponseEntity.ok(ApiResponse.success(null, "레이아웃이 저장되었습니다."));
}
@DeleteMapping("/{labelId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteBarcodeLabel(
@PathVariable String labelId) {
boolean deleted = barcodeLabelService.deleteBarcodeLabel(labelId);
if (!deleted) return ResponseEntity.status(404).body(ApiResponse.error("라벨을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null, "라벨이 삭제되었습니다."));
}
}
@@ -0,0 +1,116 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.BatchService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/batch-configs")
@RequiredArgsConstructor
@Slf4j
public class BatchController {
private final BatchService batchService;
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getBatchList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(batchService.getBatchList(params)));
}
@GetMapping("/connections")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getConnections(
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(batchService.getConnections(companyCode)));
}
@GetMapping("/connections/{type}/tables")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTablesByType(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String type) {
return ResponseEntity.ok(ApiResponse.success(batchService.getTables(type, null)));
}
@GetMapping("/connections/{type}/{connectionId}/tables")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTablesByTypeAndId(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String type,
@PathVariable Long connectionId) {
return ResponseEntity.ok(ApiResponse.success(batchService.getTables(type, connectionId)));
}
@GetMapping("/connections/{type}/tables/{tableName}/columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getColumnsByType(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String type,
@PathVariable String tableName) {
return ResponseEntity.ok(ApiResponse.success(batchService.getColumns(type, null, tableName)));
}
@GetMapping("/connections/{type}/{connectionId}/tables/{tableName}/columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getColumnsByTypeAndId(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String type,
@PathVariable Long connectionId,
@PathVariable String tableName) {
return ResponseEntity.ok(ApiResponse.success(batchService.getColumns(type, connectionId, tableName)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBatchInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
Map<String, Object> result = batchService.getBatchInfo(params);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("배치 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertBatch(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("created_by", userId);
body.put("updated_by", userId);
Map<String, Object> created = batchService.insertBatch(body);
return ResponseEntity.status(201).body(ApiResponse.success(created, "배치 설정이 성공적으로 생성되었습니다."));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateBatch(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("updated_by", userId);
body.put("id", id);
Map<String, Object> updated = batchService.updateBatch(body);
return ResponseEntity.ok(ApiResponse.success(updated, "배치 설정이 성공적으로 수정되었습니다."));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteBatch(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("updatedBy", userId);
params.put("id", id);
batchService.deleteBatch(params);
return ResponseEntity.ok(ApiResponse.success(null, "배치 설정이 성공적으로 삭제되었습니다."));
}
}
@@ -0,0 +1,80 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.BatchExecutionLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/batch-execution-log")
@RequiredArgsConstructor
@Slf4j
public class BatchExecutionLogController {
private final BatchExecutionLogService batchExecutionLogService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBatchExecutionLogList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(batchExecutionLogService.getBatchExecutionLogList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBatchExecutionLogInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(batchExecutionLogService.getBatchExecutionLogInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertBatchExecutionLog(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(batchExecutionLogService.insertBatchExecutionLog(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateBatchExecutionLog(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(batchExecutionLogService.updateBatchExecutionLog(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteBatchExecutionLog(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(batchExecutionLogService.deleteBatchExecutionLog(params)));
}
@GetMapping("/latest/{batchConfigId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBatchExecutionLogLatest(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long batchConfigId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("batchConfigId", batchConfigId);
return ResponseEntity.ok(ApiResponse.success(batchExecutionLogService.getBatchExecutionLogLatest(params)));
}
@GetMapping("/stats")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBatchExecutionLogStats(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(batchExecutionLogService.getBatchExecutionLogStats(params)));
}
}
@@ -0,0 +1,205 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.BatchManagementService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/batch-management")
@RequiredArgsConstructor
@Slf4j
public class BatchManagementController {
private final BatchManagementService batchManagementService;
// ── Dashboard Stats ───────────────────────────────────────────────────────
/** GET /api/batch-management/stats — 배치 대시보드 통계 */
@GetMapping("/stats")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBatchStats(
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(batchManagementService.getBatchStats(params)));
}
// ── Node Flows ────────────────────────────────────────────────────────────
/** GET /api/batch-management/node-flows — 배치 설정에서 노드 플로우 선택용 목록 */
@GetMapping("/node-flows")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getNodeFlows(
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(batchManagementService.getNodeFlows(params)));
}
// ── Connections ───────────────────────────────────────────────────────────
/** GET /api/batch-management/connections — 사용 가능한 커넥션 목록 조회 */
@GetMapping("/connections")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAvailableConnections(
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
batchManagementService.getAvailableConnections(companyCode)));
}
/** GET /api/batch-management/connections/:type/tables — 내부 DB 테이블 목록 */
@GetMapping("/connections/{type}/tables")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTablesFromConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String type) {
return ResponseEntity.ok(ApiResponse.success(
batchManagementService.getTablesFromConnection(type, null, companyCode)));
}
/** GET /api/batch-management/connections/:type/:id/tables — 외부 DB 테이블 목록 */
@GetMapping("/connections/{type}/{id}/tables")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTablesFromExternalConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String type,
@PathVariable Long id) {
return ResponseEntity.ok(ApiResponse.success(
batchManagementService.getTablesFromConnection(type, id, companyCode)));
}
/** GET /api/batch-management/connections/:type/tables/:tableName/columns — 내부 DB 컬럼 정보 */
@GetMapping("/connections/{type}/tables/{tableName}/columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTableColumns(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String type,
@PathVariable String tableName) {
return ResponseEntity.ok(ApiResponse.success(
batchManagementService.getTableColumns(type, null, tableName, companyCode)));
}
/** GET /api/batch-management/connections/:type/:id/tables/:tableName/columns — 외부 DB 컬럼 정보 */
@GetMapping("/connections/{type}/{id}/tables/{tableName}/columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getExternalTableColumns(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String type,
@PathVariable Long id,
@PathVariable String tableName) {
return ResponseEntity.ok(ApiResponse.success(
batchManagementService.getTableColumns(type, id, tableName, companyCode)));
}
// ── Batch Configs ─────────────────────────────────────────────────────────
/** POST /api/batch-management/batch-configs — 배치 설정 생성 */
@PostMapping("/batch-configs")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertBatchConfig(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
Map<String, Object> result = batchManagementService.insertBatchConfig(body);
return ResponseEntity.status(201).body(ApiResponse.success(result, "배치 설정이 성공적으로 생성되었습니다."));
}
/** GET /api/batch-management/batch-configs — 배치 설정 목록 조회 */
@GetMapping("/batch-configs")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBatchConfigList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(batchManagementService.getBatchConfigList(params)));
}
/** GET /api/batch-management/batch-configs/:id — 특정 배치 설정 조회 */
@GetMapping("/batch-configs/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBatchConfigInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
Map<String, Object> result = batchManagementService.getBatchConfigInfo(params);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("배치 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
/** GET /api/batch-management/batch-configs/:id/sparkline — 최근 24시간 1시간 단위 실행 집계 */
@GetMapping("/batch-configs/{id}/sparkline")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getBatchSparkline(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("batchConfigId", id);
return ResponseEntity.ok(ApiResponse.success(batchManagementService.getBatchSparkline(params)));
}
/** GET /api/batch-management/batch-configs/:id/recent-logs — 최근 실행 로그 (최대 20건) */
@GetMapping("/batch-configs/{id}/recent-logs")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getBatchRecentLogs(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("batchConfigId", id);
return ResponseEntity.ok(ApiResponse.success(batchManagementService.getBatchRecentLogs(params)));
}
/** PUT /api/batch-management/batch-configs/:id — 배치 설정 업데이트 */
@PutMapping("/batch-configs/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateBatchConfig(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(
batchManagementService.updateBatchConfig(body), "배치 설정이 성공적으로 업데이트되었습니다."));
}
/** POST /api/batch-management/batch-configs/:id/execute — 배치 수동 실행 */
@PostMapping("/batch-configs/{id}/execute")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeBatchConfig(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(
batchManagementService.executeBatchConfig(params), "배치가 성공적으로 실행되었습니다."));
}
// ── REST API ──────────────────────────────────────────────────────────────
/** POST /api/batch-management/rest-api/preview — REST API 데이터 미리보기 */
@PostMapping("/rest-api/preview")
public ResponseEntity<ApiResponse<Map<String, Object>>> previewRestApiData(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(batchManagementService.previewRestApiData(body)));
}
/** POST /api/batch-management/rest-api/save — REST API 배치 저장 */
@PostMapping("/rest-api/save")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveRestApiBatch(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
batchManagementService.saveRestApiBatch(body), "REST API 배치가 성공적으로 저장되었습니다."));
}
// ── Auth Services ─────────────────────────────────────────────────────────
/** GET /api/batch-management/auth-services — 인증 토큰 서비스명 목록 조회 */
@GetMapping("/auth-services")
public ResponseEntity<ApiResponse<List<String>>> getAuthServiceNames(
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(batchManagementService.getAuthServiceNames(params)));
}
}
@@ -0,0 +1,66 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.BomService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/bom")
@RequiredArgsConstructor
@Slf4j
public class BomController {
private final BomService bomService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBomList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(bomService.getBomList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBomInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(bomService.getBomInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertBom(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(bomService.insertBom(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateBom(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(bomService.updateBom(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteBom(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(bomService.deleteBom(params)));
}
}
@@ -0,0 +1,57 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.BookingService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* 예약 요청 API
* Node.js: app.use("/api/booking", bookingRoutes)
*/
@RestController
@RequestMapping("/api/booking")
@RequiredArgsConstructor
@Slf4j
public class BookingController {
private final BookingService bookingService;
/** GET /list — 목록 조회 (alias) */
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getBookingListAlias(
@RequestParam Map<String, Object> params) {
return ResponseEntity.ok(ApiResponse.success(bookingService.getBookingList(params)));
}
/** GET / — 예약 목록 조회, 응답: { bookings, newCount } */
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getBookingList(
@RequestParam Map<String, Object> params) {
return ResponseEntity.ok(ApiResponse.success(bookingService.getBookingList(params)));
}
/** POST /{id}/accept — 예약 수락 */
@PostMapping("/{id}/accept")
public ResponseEntity<ApiResponse<Map<String, Object>>> acceptBooking(
@PathVariable String id) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(bookingService.acceptBooking(params)));
}
/** POST /{id}/reject — 예약 거부 */
@PostMapping("/{id}/reject")
public ResponseEntity<ApiResponse<Map<String, Object>>> rejectBooking(
@PathVariable String id,
@RequestBody(required = false) Map<String, Object> body) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("id", id);
if (body != null && body.containsKey("rejectionReason")) {
params.put("rejectionReason", body.get("rejectionReason"));
}
return ResponseEntity.ok(ApiResponse.success(bookingService.rejectBooking(params)));
}
}
@@ -0,0 +1,136 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ButtonActionStandardService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/admin/button-actions")
@RequiredArgsConstructor
public class ButtonActionStandardController {
private final ButtonActionStandardService buttonActionStandardService;
/**
* GET /api/admin/button-actions
* 버튼 액션 목록 조회
*/
@GetMapping
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getButtonActions(
@RequestParam Map<String, Object> params) {
List<Map<String, Object>> data = buttonActionStandardService.getButtonActions(params);
return ResponseEntity.ok(ApiResponse.success(data, "버튼 액션 목록을 성공적으로 조회했습니다."));
}
/**
* GET /api/admin/button-actions/categories
* 버튼 액션 카테고리 목록 조회
*/
@GetMapping("/categories")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getButtonActionCategories() {
List<Map<String, Object>> data = buttonActionStandardService.getCategories(new HashMap<>());
return ResponseEntity.ok(ApiResponse.success(data, "버튼 액션 카테고리 목록을 성공적으로 조회했습니다."));
}
/**
* GET /api/admin/button-actions/{actionType}
* 버튼 액션 단건 조회
*/
@GetMapping("/{actionType}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getButtonAction(
@PathVariable String actionType) {
Map<String, Object> params = new HashMap<>();
params.put("actionType", actionType);
Map<String, Object> data = buttonActionStandardService.getButtonAction(params);
if (data == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("해당 버튼 액션을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(data, "버튼 액션 정보를 성공적으로 조회했습니다."));
}
/**
* POST /api/admin/button-actions
* 버튼 액션 생성
*/
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createButtonAction(
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
// 필수 필드 검증
if (body.get("action_type") == null || body.get("action_name") == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.error("액션 타입과 이름은 필수입니다."));
}
body.put("created_by", userId);
body.put("updated_by", userId);
// selectButtonActionByType 에서 actionType 키 사용
body.put("actionType", body.get("action_type"));
Map<String, Object> data = buttonActionStandardService.createButtonAction(body);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(data, "버튼 액션이 성공적으로 생성되었습니다."));
}
/**
* PUT /api/admin/button-actions/sort-order/bulk
* 정렬 순서 일괄 업데이트
*/
@PutMapping("/sort-order/bulk")
public ResponseEntity<ApiResponse<Void>> updateButtonActionSortOrder(
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
Object raw = body.get("buttonActions");
if (!(raw instanceof List)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.error("유효하지 않은 데이터 형식입니다."));
}
@SuppressWarnings("unchecked")
List<Map<String, Object>> items = (List<Map<String, Object>>) raw;
buttonActionStandardService.updateSortOrder(items, userId);
return ResponseEntity.ok(ApiResponse.success(null, "버튼 액션 정렬 순서가 성공적으로 업데이트되었습니다."));
}
/**
* PUT /api/admin/button-actions/{actionType}
* 버튼 액션 수정
*/
@PutMapping("/{actionType}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateButtonAction(
@PathVariable String actionType,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
body.put("actionType", actionType);
body.put("updated_by", userId);
Map<String, Object> data = buttonActionStandardService.updateButtonAction(body);
if (data == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("해당 버튼 액션을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(data, "버튼 액션이 성공적으로 수정되었습니다."));
}
/**
* DELETE /api/admin/button-actions/{actionType}
* 버튼 액션 삭제
*/
@DeleteMapping("/{actionType}")
public ResponseEntity<ApiResponse<Void>> deleteButtonAction(
@PathVariable String actionType) {
Map<String, Object> params = new HashMap<>();
params.put("actionType", actionType);
boolean deleted = buttonActionStandardService.deleteButtonAction(params);
if (!deleted) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("해당 버튼 액션을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "버튼 액션이 성공적으로 삭제되었습니다."));
}
}
@@ -0,0 +1,221 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ButtonDataflowService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 버튼 데이터플로우 컨트롤러 (/api/test-button-dataflow)
*
* Node.js buttonDataflowRoutes.ts 포팅.
*/
@RestController
@RequestMapping("/api/test-button-dataflow")
@RequiredArgsConstructor
@Slf4j
public class ButtonDataflowController {
private final ButtonDataflowService service;
// ──────────────────────────────────────────────────────────────
// 버튼 설정 관리
// ──────────────────────────────────────────────────────────────
/** GET /config/:buttonId */
@GetMapping("/config/{buttonId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getButtonDataflowConfig(
@PathVariable String buttonId) {
try {
if (buttonId == null || buttonId.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("버튼 ID가 필요합니다."));
}
return ResponseEntity.ok(ApiResponse.success(service.getButtonDataflowConfig(buttonId)));
} catch (Exception e) {
log.error("버튼 설정 조회 오류: buttonId={}", buttonId, e);
return ResponseEntity.status(500).body(ApiResponse.error("버튼 설정 조회 중 오류가 발생했습니다."));
}
}
/** PUT /config/:buttonId */
@PutMapping("/config/{buttonId}")
public ResponseEntity<ApiResponse<Void>> updateButtonDataflowConfig(
@PathVariable String buttonId,
@RequestBody Map<String, Object> config) {
try {
if (buttonId == null || buttonId.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("버튼 ID가 필요합니다."));
}
service.updateButtonDataflowConfig(buttonId, config);
return ResponseEntity.ok(ApiResponse.success(null, "버튼 설정이 업데이트되었습니다."));
} catch (Exception e) {
log.error("버튼 설정 업데이트 오류: buttonId={}", buttonId, e);
return ResponseEntity.status(500).body(ApiResponse.error("버튼 설정 업데이트 중 오류가 발생했습니다."));
}
}
// ──────────────────────────────────────────────────────────────
// 관계도 및 관계 정보 조회
// ──────────────────────────────────────────────────────────────
/** GET /diagrams */
@GetMapping("/diagrams")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAvailableDiagrams(
@RequestAttribute("companyCode") String companyCode) {
try {
if (companyCode == null || companyCode.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("회사 코드가 필요합니다."));
}
return ResponseEntity.ok(ApiResponse.success(service.getAvailableDiagrams(companyCode)));
} catch (Exception e) {
log.error("관계도 목록 조회 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("관계도 목록 조회 중 오류가 발생했습니다."));
}
}
/** GET /diagrams/:diagramId/relationships */
@GetMapping("/diagrams/{diagramId}/relationships")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDiagramRelationships(
@PathVariable int diagramId,
@RequestAttribute("companyCode") String companyCode) {
try {
if (companyCode == null || companyCode.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("회사 코드가 필요합니다."));
}
List<Map<String, Object>> result = service.getDiagramRelationships(diagramId, companyCode);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("관계도를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("관계 목록 조회 오류: diagramId={}", diagramId, e);
return ResponseEntity.status(500).body(ApiResponse.error("관계 목록 조회 중 오류가 발생했습니다."));
}
}
/** GET /diagrams/:diagramId/relationships/:relationshipId/preview */
@GetMapping("/diagrams/{diagramId}/relationships/{relationshipId}/preview")
public ResponseEntity<ApiResponse<Map<String, Object>>> getRelationshipPreview(
@PathVariable int diagramId,
@PathVariable String relationshipId,
@RequestAttribute("companyCode") String companyCode) {
try {
if (companyCode == null || companyCode.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("관계도 ID, 관계 ID, 회사 코드가 필요합니다."));
}
Map<String, Object> preview = service.getRelationshipPreview(diagramId, relationshipId, companyCode);
if (preview == null) {
return ResponseEntity.status(404).body(ApiResponse.error("관계도를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(preview));
} catch (Exception e) {
log.error("관계 미리보기 조회 오류: diagramId={}, relationshipId={}", diagramId, relationshipId, e);
return ResponseEntity.status(500).body(ApiResponse.error("관계 미리보기 조회 중 오류가 발생했습니다."));
}
}
// ──────────────────────────────────────────────────────────────
// 버튼 실행
// ──────────────────────────────────────────────────────────────
/** POST /execute-optimized (메인 최적화 실행) */
@PostMapping("/execute-optimized")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeOptimizedButton(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
return doExecuteOptimized(companyCode, body);
}
/** POST /execute (레거시 — executeOptimized 와 동일) */
@PostMapping("/execute")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeLegacy(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
return doExecuteOptimized(companyCode, body);
}
/** POST /execute-background (레거시 — executeOptimized 와 동일) */
@PostMapping("/execute-background")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeBackground(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
return doExecuteOptimized(companyCode, body);
}
/** POST /execute-simple */
@PostMapping("/execute-simple")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeSimpleDataflow(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
try {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.executeSimpleDataflow(body)));
} catch (Exception e) {
log.error("단순 데이터플로우 실행 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("간단한 제어관리 실행 중 오류가 발생했습니다."));
}
}
// ──────────────────────────────────────────────────────────────
// 작업 상태 조회
// ──────────────────────────────────────────────────────────────
/** GET /job-status/:jobId */
@GetMapping("/job-status/{jobId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getJobStatus(@PathVariable String jobId) {
try {
return ResponseEntity.ok(ApiResponse.success(service.getJobStatus(jobId)));
} catch (Exception e) {
log.error("작업 상태 조회 오류: jobId={}", jobId, e);
return ResponseEntity.status(500).body(ApiResponse.error("작업 상태 조회 중 오류가 발생했습니다."));
}
}
// ──────────────────────────────────────────────────────────────
// 테이블 조인 관계 조회
// ──────────────────────────────────────────────────────────────
/** GET /join-relationship/:mainTable/:detailTable */
@GetMapping("/join-relationship/{mainTable}/{detailTable}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getJoinRelationship(
@PathVariable String mainTable,
@PathVariable String detailTable,
@RequestAttribute("companyCode") String companyCode) {
try {
if (mainTable == null || detailTable == null) {
return ResponseEntity.status(400).body(ApiResponse.error("메인 테이블과 디테일 테이블이 필요합니다."));
}
Map<String, Object> result = service.getJoinRelationship(mainTable, detailTable, companyCode);
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("조인 관계 조회 오류: {}/{}", mainTable, detailTable, e);
return ResponseEntity.status(500).body(ApiResponse.error("조인 관계 조회 실패: " + e.getMessage()));
}
}
// ──────────────────────────────────────────────────────────────
// Private
// ──────────────────────────────────────────────────────────────
private ResponseEntity<ApiResponse<Map<String, Object>>> doExecuteOptimized(
String companyCode, Map<String, Object> body) {
try {
String buttonId = (String) body.get("buttonId");
String actionType = (String) body.get("actionType");
if (buttonId == null || actionType == null) {
return ResponseEntity.status(400).body(ApiResponse.error("필수 파라미터가 누락되었습니다."));
}
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.executeOptimizedButton(body)));
} catch (Exception e) {
log.error("버튼 실행 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("버튼 실행 중 오류가 발생했습니다."));
}
}
}
@@ -0,0 +1,125 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.CascadingAutoFillService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/cascading-auto-fill")
@RequiredArgsConstructor
@Slf4j
public class CascadingAutoFillController {
private final CascadingAutoFillService cascadingAutoFillService;
// Pipeline api_test compatibility alias
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getGroupListAlias(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(cascadingAutoFillService.getCascadingAutoFillGroupList(params)));
}
@GetMapping("/groups")
public ResponseEntity<ApiResponse<Map<String, Object>>> getGroupList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(cascadingAutoFillService.getCascadingAutoFillGroupList(params)));
}
@GetMapping("/groups/{groupCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getGroupDetail(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String groupCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("groupCode", groupCode);
Map<String, Object> result = cascadingAutoFillService.getCascadingAutoFillGroupDetail(params);
if (result == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("자동 입력 그룹을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping("/groups")
public ResponseEntity<ApiResponse<Map<String, Object>>> createGroup(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(cascadingAutoFillService.insertCascadingAutoFillGroup(body)));
}
@PutMapping("/groups/{groupCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateGroup(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String groupCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("groupCode", groupCode);
Map<String, Object> result = cascadingAutoFillService.updateCascadingAutoFillGroup(body);
if (result == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("자동 입력 그룹을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@DeleteMapping("/groups/{groupCode}")
public ResponseEntity<ApiResponse<Void>> deleteGroup(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String groupCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("groupCode", groupCode);
boolean deleted = cascadingAutoFillService.deleteCascadingAutoFillGroup(params);
if (!deleted) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("자동 입력 그룹을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null));
}
@GetMapping("/options/{groupCode}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMasterOptions(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String groupCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("groupCode", groupCode);
List<Map<String, Object>> result = cascadingAutoFillService.getAutoFillMasterOptions(params);
if (result == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("자동 입력 그룹을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/data/{groupCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getAutoFillData(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String groupCode,
@RequestParam(required = false) String masterValue) {
if (masterValue == null || masterValue.isBlank()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.error("masterValue 파라미터가 필요합니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("groupCode", groupCode);
params.put("masterValue", masterValue);
Map<String, Object> result = cascadingAutoFillService.getAutoFillData(params);
if (result == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("자동 입력 그룹을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
}
@@ -0,0 +1,81 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.CascadingConditionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/cascading-condition")
@RequiredArgsConstructor
@Slf4j
public class CascadingConditionController {
private final CascadingConditionService cascadingConditionService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCascadingConditionListAlias(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(cascadingConditionService.getCascadingConditionList(params)));
}
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getCascadingConditionList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(cascadingConditionService.getCascadingConditionList(params)));
}
@GetMapping("/filtered-options/{relationCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getFilteredOptions(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String relationCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
params.put("relationCode", relationCode);
return ResponseEntity.ok(ApiResponse.success(cascadingConditionService.getFilteredOptions(params)));
}
@GetMapping("/{conditionId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCascadingConditionInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long conditionId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("conditionId", conditionId);
return ResponseEntity.ok(ApiResponse.success(cascadingConditionService.getCascadingConditionInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertCascadingCondition(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(cascadingConditionService.insertCascadingCondition(body)));
}
@PutMapping("/{conditionId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateCascadingCondition(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long conditionId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("conditionId", conditionId);
return ResponseEntity.ok(ApiResponse.success(cascadingConditionService.updateCascadingCondition(body)));
}
@DeleteMapping("/{conditionId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteCascadingCondition(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long conditionId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("conditionId", conditionId);
return ResponseEntity.ok(ApiResponse.success(cascadingConditionService.deleteCascadingCondition(params)));
}
}
@@ -0,0 +1,157 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.CascadingHierarchyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/cascading-hierarchy")
@RequiredArgsConstructor
@Slf4j
public class CascadingHierarchyController {
private final CascadingHierarchyService cascadingHierarchyService;
// Pipeline api_test compatibility alias
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getGroupListAlias(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(cascadingHierarchyService.getCascadingHierarchyGroupList(params)));
}
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getGroupList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(cascadingHierarchyService.getCascadingHierarchyGroupList(params)));
}
@GetMapping("/{groupCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getGroupDetail(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String groupCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("groupCode", groupCode);
Map<String, Object> result = cascadingHierarchyService.getCascadingHierarchyGroupDetail(params);
if (result == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("계층 그룹을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createGroup(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
if (userId != null) body.put("userId", userId);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(cascadingHierarchyService.insertCascadingHierarchyGroup(body)));
}
@PutMapping("/{groupCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateGroup(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String groupCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("groupCode", groupCode);
if (userId != null) body.put("userId", userId);
Map<String, Object> result = cascadingHierarchyService.updateCascadingHierarchyGroup(body);
if (result == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("계층 그룹을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@DeleteMapping("/{groupCode}")
public ResponseEntity<ApiResponse<Void>> deleteGroup(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String groupCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("groupCode", groupCode);
boolean deleted = cascadingHierarchyService.deleteCascadingHierarchyGroup(params);
if (!deleted) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("계층 그룹을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null));
}
@PostMapping("/{groupCode}/levels")
public ResponseEntity<ApiResponse<Map<String, Object>>> addLevel(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String groupCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("groupCode", groupCode);
Map<String, Object> result = cascadingHierarchyService.addCascadingHierarchyLevel(body);
if (result == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("계층 그룹을 찾을 수 없습니다."));
}
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(result));
}
@PutMapping("/levels/{levelId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateLevel(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long levelId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("levelId", levelId);
Map<String, Object> result = cascadingHierarchyService.updateCascadingHierarchyLevel(body);
if (result == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("레벨을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@DeleteMapping("/levels/{levelId}")
public ResponseEntity<ApiResponse<Void>> deleteLevel(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long levelId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("levelId", levelId);
boolean deleted = cascadingHierarchyService.deleteCascadingHierarchyLevel(params);
if (!deleted) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("레벨을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null));
}
@GetMapping("/{groupCode}/options/{levelOrder}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getLevelOptions(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String groupCode,
@PathVariable Integer levelOrder,
@RequestParam(required = false) String parentValue) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("groupCode", groupCode);
params.put("levelOrder", levelOrder);
if (parentValue != null) params.put("parentValue", parentValue);
Map<String, Object> result = cascadingHierarchyService.getLevelOptions(params);
if (result == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("레벨을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
}
@@ -0,0 +1,121 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.CascadingMutualExclusionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* 상호 배제 API
* Node.js: app.use("/api/cascading-mutual-exclusion", cascadingMutualExclusionRoutes)
*/
@RestController
@RequestMapping("/api/cascading-mutual-exclusion")
@RequiredArgsConstructor
@Slf4j
public class CascadingMutualExclusionController {
private final CascadingMutualExclusionService cascadingMutualExclusionService;
/** GET /list — 목록 조회 (alias) */
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCascadingMutualExclusionListAlias(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
cascadingMutualExclusionService.getCascadingMutualExclusionList(params)));
}
/** GET / — 목록 조회 */
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getCascadingMutualExclusionList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
cascadingMutualExclusionService.getCascadingMutualExclusionList(params)));
}
/** GET /{exclusionId} — 상세 조회 */
@GetMapping("/{exclusionId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCascadingMutualExclusionInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long exclusionId) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("companyCode", companyCode);
params.put("id", exclusionId);
return ResponseEntity.ok(ApiResponse.success(
cascadingMutualExclusionService.getCascadingMutualExclusionInfo(params)));
}
/** POST / — 생성 */
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertCascadingMutualExclusion(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
cascadingMutualExclusionService.insertCascadingMutualExclusion(body)));
}
/** PUT /{exclusionId} — 수정 */
@PutMapping("/{exclusionId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateCascadingMutualExclusion(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long exclusionId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", exclusionId);
return ResponseEntity.ok(ApiResponse.success(
cascadingMutualExclusionService.updateCascadingMutualExclusion(body)));
}
/** DELETE /{exclusionId} — 하드 삭제 */
@DeleteMapping("/{exclusionId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteCascadingMutualExclusion(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long exclusionId) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("companyCode", companyCode);
params.put("id", exclusionId);
return ResponseEntity.ok(ApiResponse.success(
cascadingMutualExclusionService.deleteCascadingMutualExclusion(params)));
}
/**
* POST /validate/{exclusionCode} — 상호 배제 검증
* body: { "fieldValues": { "fieldA": "val1", "fieldB": "val1" } }
*/
@PostMapping("/validate/{exclusionCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> validateCascadingMutualExclusion(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String exclusionCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("code", exclusionCode);
return ResponseEntity.ok(ApiResponse.success(
cascadingMutualExclusionService.validateCascadingMutualExclusion(body)));
}
/**
* GET /options/{exclusionCode} — 배제 옵션 조회
* query: selectedValues (콤마 구분된 이미 선택된 값들)
*/
@GetMapping("/options/{exclusionCode}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getExcludedOptions(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String exclusionCode,
@RequestParam(required = false) String currentField,
@RequestParam(required = false) String selectedValues) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("companyCode", companyCode);
params.put("code", exclusionCode);
params.put("currentField", currentField);
params.put("selectedValues", selectedValues);
return ResponseEntity.ok(ApiResponse.success(
cascadingMutualExclusionService.getExcludedOptions(params)));
}
}
@@ -0,0 +1,139 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.CascadingRelationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* 연쇄 관계 API
* Node.js: app.use("/api/cascading-relation", cascadingRelationRoutes)
*/
@RestController
@RequestMapping("/api/cascading-relation")
@RequiredArgsConstructor
@Slf4j
public class CascadingRelationController {
private final CascadingRelationService cascadingRelationService;
/** GET /api/cascading-relation/list — 목록 조회 (alias) */
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCascadingRelationListAlias(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
cascadingRelationService.getCascadingRelationList(params)));
}
/** GET /api/cascading-relation — 목록 조회 */
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getCascadingRelationList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
cascadingRelationService.getCascadingRelationList(params)));
}
/** GET /api/cascading-relation/{id} — 상세 조회 */
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCascadingRelationInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(
cascadingRelationService.getCascadingRelationInfo(params)));
}
/** GET /api/cascading-relation/code/{code} — 코드로 단건 조회 */
@GetMapping("/code/{code}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCascadingRelationByCode(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String code) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("companyCode", companyCode);
params.put("code", code);
return ResponseEntity.ok(ApiResponse.success(
cascadingRelationService.getCascadingRelationByCode(params)));
}
/**
* GET /api/cascading-relation/parent-options/{code}
* 부모 옵션 조회 (parent_table 동적 쿼리)
*/
@GetMapping("/parent-options/{code}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getParentOptions(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String code) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("companyCode", companyCode);
params.put("code", code);
return ResponseEntity.ok(ApiResponse.success(
cascadingRelationService.getParentOptions(params)));
}
/**
* GET /api/cascading-relation/options/{code}?parentValue=&parentValues=
* 연쇄 자식 옵션 조회 (child_table 동적 쿼리)
*/
@GetMapping("/options/{code}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCascadingOptions(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String code,
@RequestParam(required = false) String parentValue,
@RequestParam(required = false) String parentValues) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("companyCode", companyCode);
params.put("code", code);
params.put("parentValue", parentValue);
params.put("parentValues", parentValues);
return ResponseEntity.ok(ApiResponse.success(
cascadingRelationService.getCascadingOptions(params)));
}
/** POST /api/cascading-relation — 생성 */
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertCascadingRelation(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("userId", userId != null ? userId : "system");
return ResponseEntity.ok(ApiResponse.success(
cascadingRelationService.insertCascadingRelation(body)));
}
/** PUT /api/cascading-relation/{id} — 수정 */
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateCascadingRelation(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("userId", userId != null ? userId : "system");
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(
cascadingRelationService.updateCascadingRelation(body)));
}
/** DELETE /api/cascading-relation/{id} — 소프트 삭제 (is_active = 'N') */
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteCascadingRelation(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable Long id) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("companyCode", companyCode);
params.put("userId", userId != null ? userId : "system");
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(
cascadingRelationService.deleteCascadingRelation(params)));
}
}
@@ -0,0 +1,191 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.CategoryTreeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/category-tree")
@RequiredArgsConstructor
public class CategoryTreeController {
private final CategoryTreeService categoryTreeService;
/**
* GET /api/category-tree/test/all-category-keys
* 전체 카테고리 키 목록 조회 (모든 테이블.컬럼 조합)
* 주의: /test/{tableName}/{columnName} 보다 먼저 매핑되어야 함
*/
@GetMapping("/test/all-category-keys")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCategoryTreeKeyList(
@RequestAttribute("companyCode") String companyCode) {
List<Map<String, Object>> keys = categoryTreeService.getCategoryTreeKeyList(companyCode);
return ResponseEntity.ok(ApiResponse.success(keys));
}
/**
* GET /api/category-tree/test/{tableName}/{columnName}
* 카테고리 트리 조회
*/
@GetMapping("/test/{tableName}/{columnName}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCategoryTreeList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@PathVariable String columnName) {
List<Map<String, Object>> tree = categoryTreeService.getCategoryTreeList(companyCode, tableName, columnName);
return ResponseEntity.ok(ApiResponse.success(tree));
}
/**
* GET /api/category-tree/test/{tableName}/{columnName}/flat
* 카테고리 플랫 리스트 조회
*/
@GetMapping("/test/{tableName}/{columnName}/flat")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCategoryTreeFlatList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@PathVariable String columnName) {
List<Map<String, Object>> list = categoryTreeService.getCategoryTreeFlatList(companyCode, tableName, columnName);
return ResponseEntity.ok(ApiResponse.success(list));
}
/**
* GET /api/category-tree/test/value/{valueId}
* 카테고리 값 단건 조회
*/
@GetMapping("/test/value/{valueId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCategoryTreeInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int valueId) {
Map<String, Object> value = categoryTreeService.getCategoryTreeInfo(companyCode, valueId);
if (value == null) {
return ResponseEntity.status(404).body(ApiResponse.error("카테고리 값을 찾을 수 없습니다"));
}
return ResponseEntity.ok(ApiResponse.success(value));
}
/**
* POST /api/category-tree/test/value
* 카테고리 값 생성
*/
@PostMapping("/test/value")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertCategoryTree(
@RequestAttribute("companyCode") String userCompanyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
if (body.get("tableName") == null || body.get("columnName") == null
|| body.get("valueCode") == null || body.get("valueLabel") == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("tableName, columnName, valueCode, valueLabel은 필수입니다"));
}
// 최고 관리자(*) 는 targetCompanyCode 로 회사 코드 오버라이드 가능
String companyCode = userCompanyCode;
String targetCompanyCode = (String) body.get("targetCompanyCode");
if (targetCompanyCode != null && "*".equals(userCompanyCode)) {
companyCode = targetCompanyCode;
}
try {
Map<String, Object> value = categoryTreeService.insertCategoryTree(body, companyCode, userId);
return ResponseEntity.ok(ApiResponse.success(value));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("카테고리 값 생성 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error(e.getMessage()));
}
}
/**
* PUT /api/category-tree/test/value/{valueId}
* 카테고리 값 수정
*/
@PutMapping("/test/value/{valueId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateCategoryTree(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable int valueId,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> value = categoryTreeService.updateCategoryTree(companyCode, valueId, body, userId);
if (value == null) {
return ResponseEntity.status(404).body(ApiResponse.error("카테고리 값을 찾을 수 없습니다"));
}
return ResponseEntity.ok(ApiResponse.success(value));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("카테고리 값 수정 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error(e.getMessage()));
}
}
/**
* GET /api/category-tree/test/value/{valueId}/can-delete
* 카테고리 값 삭제 가능 여부 사전 확인
*/
@GetMapping("/test/value/{valueId}/can-delete")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkCanDelete(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int valueId) {
Map<String, Object> result = categoryTreeService.checkCanDelete(companyCode, valueId);
return ResponseEntity.ok(ApiResponse.success(result));
}
/**
* DELETE /api/category-tree/test/value/{valueId}
* 카테고리 값 삭제
*/
@DeleteMapping("/test/value/{valueId}")
public ResponseEntity<ApiResponse<Void>> deleteCategoryTree(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int valueId) {
try {
boolean deleted = categoryTreeService.deleteCategoryTree(companyCode, valueId);
if (!deleted) {
return ResponseEntity.status(404).body(ApiResponse.error("카테고리 값을 찾을 수 없습니다"));
}
return ResponseEntity.ok(ApiResponse.success(null, "삭제되었습니다"));
} catch (IllegalStateException e) {
String msg = e.getMessage();
if (msg != null && msg.startsWith("VALIDATION:")) {
return ResponseEntity.badRequest()
.body(ApiResponse.error(msg.substring("VALIDATION:".length())));
}
log.error("카테고리 값 삭제 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error(msg));
} catch (Exception e) {
log.error("카테고리 값 삭제 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error(e.getMessage()));
}
}
/**
* GET /api/category-tree/test/columns/{tableName}
* 테이블의 카테고리 컬럼 목록 조회
*/
@GetMapping("/test/columns/{tableName}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCategoryTreeColumnList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName) {
List<Map<String, Object>> columns = categoryTreeService.getCategoryTreeColumnList(companyCode, tableName);
return ResponseEntity.ok(ApiResponse.success(columns));
}
}
@@ -0,0 +1,142 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.CategoryValueCascadingService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/category-value-cascading")
@RequiredArgsConstructor
@Slf4j
public class CategoryValueCascadingController {
private final CategoryValueCascadingService categoryValueCascadingService;
/** GET /groups → 그룹 목록 조회 */
@GetMapping("/groups")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCategoryValueCascadingGroupList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(categoryValueCascadingService.getCategoryValueCascadingGroupList(params)));
}
/** GET /groups/{groupId} → 그룹 상세 조회 (매핑 포함) */
@GetMapping("/groups/{groupId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCategoryValueCascadingGroupInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long groupId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("groupId", groupId);
Map<String, Object> result = categoryValueCascadingService.getCategoryValueCascadingGroupInfo(params);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("카테고리 값 연쇄관계 그룹을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
/** GET /code/{code} → 관계 코드로 조회 */
@GetMapping("/code/{code}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCategoryValueCascadingGroupByCode(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String code) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("code", code);
Map<String, Object> result = categoryValueCascadingService.getCategoryValueCascadingGroupByCode(params);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("카테고리 값 연쇄관계를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
/** POST /groups → 그룹 생성 */
@PostMapping("/groups")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertCategoryValueCascadingGroup(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(categoryValueCascadingService.insertCategoryValueCascadingGroup(body)));
}
/** PUT /groups/{groupId} → 그룹 수정 */
@PutMapping("/groups/{groupId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateCategoryValueCascadingGroup(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long groupId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("groupId", groupId);
return ResponseEntity.ok(ApiResponse.success(categoryValueCascadingService.updateCategoryValueCascadingGroup(body)));
}
/** DELETE /groups/{groupId} → 그룹 소프트 삭제 */
@DeleteMapping("/groups/{groupId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteCategoryValueCascadingGroup(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long groupId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("groupId", groupId);
return ResponseEntity.ok(ApiResponse.success(categoryValueCascadingService.deleteCategoryValueCascadingGroup(params)));
}
/** POST /groups/{groupId}/mappings → 매핑 일괄 저장 */
@PostMapping("/groups/{groupId}/mappings")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveCategoryValueCascadingMappings(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long groupId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("groupId", groupId);
return ResponseEntity.ok(ApiResponse.success(categoryValueCascadingService.saveCategoryValueCascadingMappings(body)));
}
/** GET /parent-options/{code} → 부모 카테고리 값 목록 */
@GetMapping("/parent-options/{code}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCategoryValueCascadingParentOptions(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String code,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
params.put("code", code);
return ResponseEntity.ok(ApiResponse.success(categoryValueCascadingService.getCategoryValueCascadingParentOptions(params)));
}
/** GET /child-options/{code} → 자식 카테고리 값 목록 */
@GetMapping("/child-options/{code}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCategoryValueCascadingChildOptions(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String code,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
params.put("code", code);
return ResponseEntity.ok(ApiResponse.success(categoryValueCascadingService.getCategoryValueCascadingChildOptions(params)));
}
/** GET /options/{code} → 연쇄 옵션 조회 (parentValue/parentValues 파라미터) */
@GetMapping("/options/{code}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCategoryValueCascadingOptions(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String code,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
params.put("code", code);
return ResponseEntity.ok(ApiResponse.success(categoryValueCascadingService.getCategoryValueCascadingOptions(params)));
}
/** GET /table/{tableName}/mappings → 테이블별 매핑 조회 */
@GetMapping("/table/{tableName}/mappings")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCategoryValueCascadingMappingsByTable(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("tableName", tableName);
return ResponseEntity.ok(ApiResponse.success(categoryValueCascadingService.getCategoryValueCascadingMappingsByTable(params)));
}
}
@@ -0,0 +1,69 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.CodeMergeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/code-merge")
@RequiredArgsConstructor
@Slf4j
public class CodeMergeController {
private final CodeMergeService codeMergeService;
/** POST /api/code-merge/merge-all-tables — columnName 기준 전체 테이블 코드 병합 */
@PostMapping("/merge-all-tables")
public ResponseEntity<ApiResponse<Map<String, Object>>> mergeAllTables(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
codeMergeService.mergeAllTables(body),
"코드 병합이 완료되었습니다."));
}
/** GET /api/code-merge/tables-with-column/:columnName — 해당 컬럼을 가진 테이블 목록 */
@GetMapping("/tables-with-column/{columnName}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTablesWithColumn(
@PathVariable String columnName) {
return ResponseEntity.ok(ApiResponse.success(
codeMergeService.getTablesWithColumn(columnName)));
}
/** POST /api/code-merge/preview — columnName + oldValue 기준 영향 미리보기 */
@PostMapping("/preview")
public ResponseEntity<ApiResponse<Map<String, Object>>> previewCodeMerge(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
codeMergeService.previewCodeMerge(body)));
}
/** POST /api/code-merge/merge-by-value — 값 기반 전체 테이블/컬럼 코드 병합 */
@PostMapping("/merge-by-value")
public ResponseEntity<ApiResponse<Map<String, Object>>> mergeByValue(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
codeMergeService.mergeByValue(body),
"코드 병합이 완료되었습니다."));
}
/** POST /api/code-merge/preview-by-value — 값 기반 영향 미리보기 */
@PostMapping("/preview-by-value")
public ResponseEntity<ApiResponse<Map<String, Object>>> previewByValue(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
codeMergeService.previewByValue(body)));
}
}
@@ -0,0 +1,99 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.CollectionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/collections")
@RequiredArgsConstructor
@Slf4j
public class CollectionController {
private final CollectionService collectionService;
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getCollectionList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(collectionService.getCollectionList(params)));
}
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCollectionListAlias(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(collectionService.getCollectionList(params)));
}
@GetMapping("/jobs/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCollectionJobList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(collectionService.getCollectionJobList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCollectionInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(collectionService.getCollectionInfo(params)));
}
@GetMapping("/{configId}/history")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCollectionHistory(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long configId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("configId", configId);
return ResponseEntity.ok(ApiResponse.success(collectionService.getCollectionHistory(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertCollection(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.status(201).body(ApiResponse.success(collectionService.insertCollection(body), "수집 설정이 생성되었습니다."));
}
@PostMapping("/{id}/execute")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeCollection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(collectionService.executeCollection(params)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateCollection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(collectionService.updateCollection(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteCollection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
collectionService.deleteCollection(params);
return ResponseEntity.ok(ApiResponse.success(null, "수집 설정이 삭제되었습니다."));
}
}
@@ -0,0 +1,326 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.CommonCodeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* Common Code Controller
*
* commonCodeRoutes.ts 포팅 — /api/common-codes 기준 15개 엔드포인트.
*
* NOTE: Spring MVC 는 리터럴 세그먼트를 경로변수보다 우선하므로
* /check-duplicate, /reorder 는 별도 선언 순서 없이도 정상 동작하나,
* 가독성을 위해 구체적인 경로를 먼저 선언한다.
*/
@RestController
@RequestMapping("/api/common-codes")
@RequiredArgsConstructor
@Slf4j
public class CommonCodeController {
private final CommonCodeService service;
// ─────────────────────────────────────────────────────────────
// GET /categories
// Node.js: { success, data: [...], total, message } (flat, not nested)
// ─────────────────────────────────────────────────────────────
@GetMapping("/categories")
public ResponseEntity<Map<String, Object>> getCommonCodeCategoryList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
Map<String, Object> svcResult = service.getCommonCodeCategoryList(params);
Map<String, Object> response = new java.util.LinkedHashMap<>();
response.put("success", true);
response.put("data", svcResult.get("data"));
response.put("total", svcResult.get("total"));
response.put("message", "카테고리 목록 조회 성공");
return ResponseEntity.ok(response);
}
// ─────────────────────────────────────────────────────────────
// GET /categories/check-duplicate ← /{categoryCode} 보다 먼저
// ─────────────────────────────────────────────────────────────
@GetMapping("/categories/check-duplicate")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkCategoryDuplicate(
@RequestAttribute("companyCode") String companyCode,
@RequestParam("categoryCode") String categoryCode) {
return ResponseEntity.ok(
ApiResponse.success(service.checkCategoryDuplicate(categoryCode, companyCode)));
}
// ─────────────────────────────────────────────────────────────
// POST /categories
// ─────────────────────────────────────────────────────────────
@PostMapping("/categories")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertCommonCodeCategory(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
if (body.get("categoryCode") == null || body.get("categoryName") == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("필수 필드가 누락되었습니다. (categoryCode, categoryName)"));
}
try {
Map<String, Object> created = service.insertCommonCodeCategory(body, companyCode, userId);
return ResponseEntity.status(201)
.body(ApiResponse.success(created, "카테고리가 성공적으로 생성되었습니다."));
} catch (Exception e) {
log.error("카테고리 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("카테고리 생성에 실패했습니다."));
}
}
// ─────────────────────────────────────────────────────────────
// PUT /categories/:categoryCode
// ─────────────────────────────────────────────────────────────
@PutMapping("/categories/{categoryCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateCommonCodeCategory(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable String categoryCode,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> updated = service.updateCommonCodeCategory(categoryCode, body, companyCode, userId);
if (updated == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("카테고리를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(updated, "카테고리가 성공적으로 수정되었습니다."));
} catch (Exception e) {
log.error("카테고리 수정 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("카테고리 수정에 실패했습니다."));
}
}
// ─────────────────────────────────────────────────────────────
// DELETE /categories/:categoryCode
// ─────────────────────────────────────────────────────────────
@DeleteMapping("/categories/{categoryCode}")
public ResponseEntity<ApiResponse<Void>> deleteCommonCodeCategory(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String categoryCode) {
try {
service.deleteCommonCodeCategory(categoryCode, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "카테고리가 성공적으로 삭제되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("카테고리 삭제 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("카테고리 삭제에 실패했습니다."));
}
}
// ─────────────────────────────────────────────────────────────
// GET /categories/:categoryCode/codes
// ─────────────────────────────────────────────────────────────
@GetMapping("/categories/{categoryCode}/codes")
public ResponseEntity<Map<String, Object>> getCommonCodeList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String categoryCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
Map<String, Object> svcResult = service.getCommonCodeList(categoryCode, params);
Map<String, Object> response = new java.util.LinkedHashMap<>();
response.put("success", true);
response.put("data", svcResult.get("data"));
response.put("total", svcResult.get("total"));
response.put("message", "코드 목록 조회 성공");
return ResponseEntity.ok(response);
}
// ─────────────────────────────────────────────────────────────
// POST /categories/:categoryCode/codes
// ─────────────────────────────────────────────────────────────
@PostMapping("/categories/{categoryCode}/codes")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertCommonCode(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable String categoryCode,
@RequestBody Map<String, Object> body) {
if (body.get("codeValue") == null || body.get("codeName") == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("필수 필드가 누락되었습니다. (codeValue, codeName)"));
}
try {
Map<String, Object> created = service.insertCommonCode(categoryCode, body, companyCode, userId);
return ResponseEntity.status(201)
.body(ApiResponse.success(created, "코드가 성공적으로 생성되었습니다."));
} catch (Exception e) {
log.error("코드 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("코드 생성에 실패했습니다."));
}
}
// ─────────────────────────────────────────────────────────────
// GET /categories/:categoryCode/codes/check-duplicate ← /{codeValue} 보다 먼저
// ─────────────────────────────────────────────────────────────
@GetMapping("/categories/{categoryCode}/codes/check-duplicate")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkCodeDuplicate(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String categoryCode,
@RequestParam("codeValue") String codeValue) {
return ResponseEntity.ok(
ApiResponse.success(service.checkCodeDuplicate(categoryCode, codeValue, companyCode)));
}
// ─────────────────────────────────────────────────────────────
// PUT /categories/:categoryCode/codes/reorder ← /{codeValue} 보다 먼저
// ─────────────────────────────────────────────────────────────
@SuppressWarnings("unchecked")
@PutMapping("/categories/{categoryCode}/codes/reorder")
public ResponseEntity<ApiResponse<Void>> updateCommonCodeOrder(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String categoryCode,
@RequestBody Map<String, Object> body) {
Object codesRaw = body.get("codes");
if (!(codesRaw instanceof List)) {
return ResponseEntity.status(400)
.body(ApiResponse.error("codes 배열이 필요합니다."));
}
try {
service.updateCommonCodeOrder(categoryCode, (List<Map<String, Object>>) codesRaw, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "정렬 순서가 변경되었습니다."));
} catch (Exception e) {
log.error("코드 정렬 변경 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("정렬 순서 변경에 실패했습니다."));
}
}
// ─────────────────────────────────────────────────────────────
// GET /categories/:categoryCode/hierarchy
// ─────────────────────────────────────────────────────────────
@GetMapping("/categories/{categoryCode}/hierarchy")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCommonCodeHierarchicalList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String categoryCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(
ApiResponse.success(service.getCommonCodeHierarchicalList(categoryCode, params)));
}
// ─────────────────────────────────────────────────────────────
// GET /categories/:categoryCode/tree
// ─────────────────────────────────────────────────────────────
@GetMapping("/categories/{categoryCode}/tree")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCommonCodeTree(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String categoryCode) {
return ResponseEntity.ok(
ApiResponse.success(service.getCommonCodeTree(categoryCode, companyCode)));
}
// ─────────────────────────────────────────────────────────────
// GET /categories/:categoryCode/options
// ─────────────────────────────────────────────────────────────
@GetMapping("/categories/{categoryCode}/options")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCommonCodeOptionList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String categoryCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(
ApiResponse.success(service.getCommonCodeOptionList(categoryCode, params)));
}
// ─────────────────────────────────────────────────────────────
// GET /categories/:categoryCode/codes/:codeValue/has-children
// ─────────────────────────────────────────────────────────────
@GetMapping("/categories/{categoryCode}/codes/{codeValue}/has-children")
public ResponseEntity<ApiResponse<Map<String, Object>>> hasChildren(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String categoryCode,
@PathVariable String codeValue) {
return ResponseEntity.ok(
ApiResponse.success(service.hasChildren(categoryCode, codeValue, companyCode)));
}
// ─────────────────────────────────────────────────────────────
// PUT /categories/:categoryCode/codes/:codeValue
// ─────────────────────────────────────────────────────────────
@PutMapping("/categories/{categoryCode}/codes/{codeValue}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateCommonCode(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable String categoryCode,
@PathVariable String codeValue,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> updated = service.updateCommonCode(categoryCode, codeValue, body, companyCode, userId);
if (updated == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("코드를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(updated, "코드가 성공적으로 수정되었습니다."));
} catch (Exception e) {
log.error("코드 수정 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("코드 수정에 실패했습니다."));
}
}
// ─────────────────────────────────────────────────────────────
// DELETE /categories/:categoryCode/codes/:codeValue
// ─────────────────────────────────────────────────────────────
@DeleteMapping("/categories/{categoryCode}/codes/{codeValue}")
public ResponseEntity<ApiResponse<Void>> deleteCommonCode(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String categoryCode,
@PathVariable String codeValue) {
try {
service.deleteCommonCode(categoryCode, codeValue, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "코드가 성공적으로 삭제되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("코드 삭제 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("코드 삭제에 실패했습니다."));
}
}
}
@@ -0,0 +1,80 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.CompanyManagementService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/company-management")
@RequiredArgsConstructor
@Slf4j
public class CompanyManagementController {
private final CompanyManagementService companyManagementService;
/**
* DELETE /api/company-management/:companyCode
* 회사 삭제 및 파일 정리 (soft delete)
*/
@DeleteMapping("/{companyCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteCompany(
@PathVariable String companyCode,
@RequestBody(required = false) Map<String, Object> body) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
if (body != null) {
params.putAll(body);
}
try {
Map<String, Object> data = companyManagementService.deleteCompany(params);
String companyName = (String) data.get("companyName");
return ResponseEntity.ok(
ApiResponse.success(data, "회사 '" + companyName + "'이(가) 성공적으로 삭제되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (RuntimeException e) {
log.error("회사 삭제 실패: companyCode={}", companyCode, e);
return ResponseEntity.status(500).body(ApiResponse.error(e.getMessage()));
}
}
/**
* GET /api/company-management/disk-usage/all
* 전체 회사 디스크 사용량 조회
* ※ /{companyCode}/disk-usage 보다 먼저 정의 (경로 특이성으로 충돌 없음)
*/
@GetMapping("/disk-usage/all")
public ResponseEntity<ApiResponse<Map<String, Object>>> getAllCompaniesDiskUsage() {
try {
Map<String, Object> data = companyManagementService.getAllCompaniesDiskUsage();
return ResponseEntity.ok(ApiResponse.success(data));
} catch (Exception e) {
log.error("전체 디스크 사용량 조회 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("전체 디스크 사용량 조회 중 오류가 발생했습니다."));
}
}
/**
* GET /api/company-management/:companyCode/disk-usage
* 회사별 디스크 사용량 조회
*/
@GetMapping("/{companyCode}/disk-usage")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCompanyDiskUsage(
@PathVariable String companyCode) {
try {
Map<String, Object> data = companyManagementService.getCompanyDiskUsage(companyCode);
return ResponseEntity.ok(ApiResponse.success(data));
} catch (Exception e) {
log.error("디스크 사용량 조회 실패: companyCode={}", companyCode, e);
return ResponseEntity.status(500).body(ApiResponse.error("디스크 사용량 조회 중 오류가 발생했습니다."));
}
}
}
@@ -0,0 +1,188 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ComponentStandardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Component Standard 컨트롤러
*
* Node.js componentStandardRoutes.ts 포팅.
*
* GET /api/component-standard — 목록 조회
* GET /api/component-standard/categories — 카테고리 목록
* GET /api/component-standard/statistics — 통계
* GET /api/component-standard/check-duplicate/{code} — 코드 중복 체크
* GET /api/component-standard/{component_code} — 단건 조회
* POST /api/component-standard — 생성
* PUT /api/component-standard/{component_code} — 수정
* DELETE /api/component-standard/{component_code} — 삭제
* PUT /api/component-standard/sort/order — 정렬 순서 업데이트
* POST /api/component-standard/duplicate — 복제
*/
@RestController
@RequestMapping("/api/component-standard")
@RequiredArgsConstructor
@Slf4j
public class ComponentStandardController {
private final ComponentStandardService service;
// ─────────────────────────────────────────────────────────
// GET / — 목록 조회
// ─────────────────────────────────────────────────────────
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getComponents(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getComponents(params)));
}
// ─────────────────────────────────────────────────────────
// GET /categories — 카테고리 목록 (반드시 /{code} 앞에 선언)
// ─────────────────────────────────────────────────────────
@GetMapping("/categories")
public ResponseEntity<ApiResponse<List<String>>> getCategories(
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getCategories(params)));
}
// ─────────────────────────────────────────────────────────
// GET /statistics — 통계
// ─────────────────────────────────────────────────────────
@GetMapping("/statistics")
public ResponseEntity<ApiResponse<Map<String, Object>>> getStatistics(
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getStatistics(params)));
}
// ─────────────────────────────────────────────────────────
// GET /check-duplicate/{component_code} — 중복 체크
// ─────────────────────────────────────────────────────────
@GetMapping("/check-duplicate/{component_code}")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkDuplicate(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("component_code") String componentCode) {
Map<String, Object> params = new HashMap<>();
params.put("component_code", componentCode);
params.put("companyCode", companyCode);
boolean exists = service.checkDuplicate(params);
return ResponseEntity.ok(ApiResponse.success(Map.of("exists", exists)));
}
// ─────────────────────────────────────────────────────────
// GET /{component_code} — 단건 조회
// ─────────────────────────────────────────────────────────
@GetMapping("/{component_code}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getComponent(
@PathVariable("component_code") String componentCode) {
try {
return ResponseEntity.ok(ApiResponse.success(service.getComponent(componentCode)));
} catch (RuntimeException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
// ─────────────────────────────────────────────────────────
// POST / — 생성
// ─────────────────────────────────────────────────────────
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createComponent(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.putIfAbsent("company_code", companyCode);
try {
return ResponseEntity.ok(ApiResponse.success(service.createComponent(body)));
} catch (RuntimeException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
// ─────────────────────────────────────────────────────────
// PUT /sort/order — 정렬 순서 업데이트 (/{code} 보다 먼저 선언)
// ─────────────────────────────────────────────────────────
@PutMapping("/sort/order")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateSortOrder(
@RequestBody Map<String, Object> body) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> updates = (List<Map<String, Object>>) body.get("updates");
if (updates == null || updates.isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error("updates 항목이 필요합니다."));
}
return ResponseEntity.ok(ApiResponse.success(service.updateSortOrder(updates)));
}
// ─────────────────────────────────────────────────────────
// PUT /{component_code} — 수정
// ─────────────────────────────────────────────────────────
@PutMapping("/{component_code}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateComponent(
@PathVariable("component_code") String componentCode,
@RequestBody Map<String, Object> body) {
try {
return ResponseEntity.ok(ApiResponse.success(service.updateComponent(componentCode, body)));
} catch (RuntimeException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
// ─────────────────────────────────────────────────────────
// DELETE /{component_code} — 삭제
// ─────────────────────────────────────────────────────────
@DeleteMapping("/{component_code}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteComponent(
@PathVariable("component_code") String componentCode) {
try {
return ResponseEntity.ok(ApiResponse.success(service.deleteComponent(componentCode)));
} catch (RuntimeException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
// ─────────────────────────────────────────────────────────
// POST /duplicate — 복제
// ─────────────────────────────────────────────────────────
@PostMapping("/duplicate")
public ResponseEntity<ApiResponse<Map<String, Object>>> duplicateComponent(
@RequestBody Map<String, Object> body) {
String sourceCode = str(body.get("source_code"));
String newCode = str(body.get("new_code"));
String newName = str(body.get("new_name"));
if (sourceCode.isEmpty() || newCode.isEmpty() || newName.isEmpty()) {
return ResponseEntity.status(400)
.body(ApiResponse.error("source_code, new_code, new_name 항목이 필요합니다."));
}
try {
return ResponseEntity.ok(ApiResponse.success(
service.duplicateComponent(sourceCode, newCode, newName)));
} catch (RuntimeException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
private String str(Object o) { return o == null ? "" : o.toString(); }
}
@@ -0,0 +1,166 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DashboardService;
import com.erp.service.ExternalRestApiConnectionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/dashboard")
@RequiredArgsConstructor
@Slf4j
public class DashboardController {
private final DashboardService dashboardService;
private final ExternalRestApiConnectionService externalRestApiConnectionService;
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getDashboards(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(dashboardService.getDashboardList(params)));
}
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDashboardsLegacy(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(dashboardService.getDashboardList(params)));
}
@GetMapping("/public")
public ResponseEntity<ApiResponse<Map<String, Object>>> getPublicDashboards(
@RequestParam Map<String, Object> params) {
params.put("companyCode", "*");
params.put("isPublic", "true");
return ResponseEntity.ok(ApiResponse.success(dashboardService.getDashboardList(params)));
}
@GetMapping("/my")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMyDashboards(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(dashboardService.getDashboardList(params)));
}
@GetMapping("/public/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getPublicDashboard(
@PathVariable String id) {
Map<String, Object> result = dashboardService.getDashboardById(id, null);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("대시보드를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDashboard(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String id) {
Map<String, Object> result = dashboardService.getDashboardById(id, companyCode);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("대시보드를 찾을 수 없거나 접근 권한이 없습니다."));
// 다른 사람 것 조회 시 조회수 증가
if (userId != null && !userId.equals(result.get("createdBy"))) {
dashboardService.incrementViewCount(id);
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createDashboard(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
String title = (String) body.get("title");
if (title == null || title.isBlank()) return ResponseEntity.status(400).body(ApiResponse.error("대시보드 제목이 필요합니다."));
Map<String, Object> result = dashboardService.createDashboard(body, userId, companyCode);
return ResponseEntity.status(201).body(ApiResponse.success(result, "대시보드가 생성되었습니다."));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateDashboard(
@PathVariable String id,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
Map<String, Object> result = dashboardService.updateDashboard(id, body, userId);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("대시보드를 찾을 수 없거나 수정 권한이 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result, "대시보드가 수정되었습니다."));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteDashboard(
@PathVariable String id,
@RequestAttribute(value = "userId", required = false) String userId) {
boolean deleted = dashboardService.deleteDashboard(id, userId);
if (!deleted) return ResponseEntity.status(404).body(ApiResponse.error("대시보드를 찾을 수 없거나 삭제 권한이 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null, "대시보드가 삭제되었습니다."));
}
@PostMapping("/execute-query")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeQuery(
@RequestBody Map<String, Object> body) {
String query = (String) body.get("query");
if (query == null || query.isBlank()) return ResponseEntity.status(400).body(ApiResponse.error("쿼리가 필요합니다."));
try {
Map<String, Object> result = dashboardService.executeQuery(query);
return ResponseEntity.ok(ApiResponse.success(result, "쿼리가 실행되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
@PostMapping("/execute-dml")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeDml(
@RequestBody Map<String, Object> body) {
String query = (String) body.get("query");
if (query == null || query.isBlank()) return ResponseEntity.status(400).body(ApiResponse.error("쿼리가 필요합니다."));
try {
Map<String, Object> result = dashboardService.executeDml(query);
return ResponseEntity.ok(ApiResponse.success(result, "쿼리가 실행되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
@PostMapping("/fetch-external-api")
public ResponseEntity<ApiResponse<Object>> fetchExternalApi(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
String url = (String) body.get("url");
if (url == null || url.isBlank()) return ResponseEntity.status(400).body(ApiResponse.error("URL이 필요합니다."));
Object externalConnectionId = body.get("externalConnectionId");
Map<String, Object> requestParams = new HashMap<>(body);
requestParams.put("companyCode", companyCode);
if (externalConnectionId != null) {
int connId = Integer.parseInt(String.valueOf(externalConnectionId));
Map<String, Object> result = externalRestApiConnectionService.fetchData(connId, url, null, requestParams);
return ResponseEntity.ok(ApiResponse.success(result));
}
// 커넥션 없이 직접 호출 - 기본 응답
Map<String, Object> result = new LinkedHashMap<>();
result.put("url", url);
result.put("message", "externalConnectionId가 필요합니다.");
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping("/table-schema")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTableSchema(
@RequestBody Map<String, Object> body) {
String tableName = (String) body.get("tableName");
if (tableName == null || tableName.isBlank()) return ResponseEntity.status(400).body(ApiResponse.error("테이블명이 필요합니다."));
try {
Map<String, Object> result = dashboardService.getTableSchema(tableName);
return ResponseEntity.ok(ApiResponse.success(result));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
}
@@ -0,0 +1,108 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DataAdvancedService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/data")
@RequiredArgsConstructor
@Slf4j
public class DataAdvancedController {
private final DataAdvancedService dataAdvancedService;
@PostMapping("/upsert-grouped")
public ResponseEntity<ApiResponse<Map<String, Object>>> upsertGrouped(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
try {
return ResponseEntity.ok(ApiResponse.success(dataAdvancedService.upsertGrouped(body)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
@PostMapping("/{tableName}/delete")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteByCondition(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("tableName", tableName);
try {
return ResponseEntity.ok(ApiResponse.success(dataAdvancedService.deleteByCondition(body)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
@PostMapping("/{tableName}/delete-group")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteGroup(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("tableName", tableName);
try {
return ResponseEntity.ok(ApiResponse.success(dataAdvancedService.deleteGroup(body)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
@GetMapping("/multi-table/auto-detect")
public ResponseEntity<ApiResponse<Map<String, Object>>> autoDetectMultiTable(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(dataAdvancedService.autoDetectMultiTable(params)));
}
@PostMapping("/multi-table/upload")
public ResponseEntity<ApiResponse<Map<String, Object>>> uploadMultiTable(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(dataAdvancedService.uploadMultiTable(body)));
}
@GetMapping("/master-detail/relation/{screenId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMasterDetailRelation(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String screenId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("screenId", screenId);
return ResponseEntity.ok(ApiResponse.success(dataAdvancedService.getMasterDetailRelation(params)));
}
@PostMapping("/master-detail/download")
public ResponseEntity<ApiResponse<Map<String, Object>>> downloadMasterDetail(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(dataAdvancedService.downloadMasterDetail(body)));
}
@PostMapping("/master-detail/upload")
public ResponseEntity<ApiResponse<Map<String, Object>>> uploadMasterDetail(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(dataAdvancedService.uploadMasterDetail(body)));
}
@PostMapping("/master-detail/upload-simple")
public ResponseEntity<ApiResponse<Map<String, Object>>> uploadMasterDetailSimple(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(dataAdvancedService.uploadMasterDetailSimple(body)));
}
}
@@ -0,0 +1,118 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DataService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/data")
@RequiredArgsConstructor
@Slf4j
public class DataController {
private final DataService dataService;
@GetMapping("/join")
public ResponseEntity<ApiResponse<Map<String, Object>>> getJoinData(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(dataService.getJoinData(params)));
}
@GetMapping("/{tableName}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTableData(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
params.put("tableName", tableName);
try {
return ResponseEntity.ok(ApiResponse.success(dataService.getTableData(params)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
@GetMapping("/{tableName}/columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTableColumns(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName) {
try {
return ResponseEntity.ok(ApiResponse.success(dataService.getTableColumns(tableName)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
@GetMapping("/{tableName}/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getRecordDetail(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@PathVariable String id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("tableName", tableName);
params.put("id", id);
try {
Map<String, Object> result = dataService.getRecordDetail(params);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("레코드를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
@PostMapping("/{tableName}")
public ResponseEntity<ApiResponse<Map<String, Object>>> createRecord(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("tableName", tableName);
try {
return ResponseEntity.status(201).body(ApiResponse.success(dataService.createRecord(body)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
@PutMapping("/{tableName}/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateRecord(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@PathVariable String id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("tableName", tableName);
body.put("id", id);
try {
Map<String, Object> result = dataService.updateRecord(body);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("레코드를 찾을 수 없거나 수정 권한이 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
@DeleteMapping("/{tableName}/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteRecord(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@PathVariable String id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("tableName", tableName);
params.put("id", id);
try {
Map<String, Object> result = dataService.deleteRecord(params);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("레코드를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
}
@@ -0,0 +1,378 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DataflowService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/dataflow")
@RequiredArgsConstructor
@Slf4j
public class DataflowController {
private final DataflowService dataflowService;
// ═══════════════════════════════════════════════════════════════════
// table-relationships
// ═══════════════════════════════════════════════════════════════════
/**
* POST /api/dataflow/table-relationships
*/
@PostMapping("/table-relationships")
public ResponseEntity<ApiResponse<Map<String, Object>>> createTableRelationship(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
String relName = str(body.get("relationshipName"));
String fromTbl = str(body.get("fromTableName"));
String fromCol = str(body.get("fromColumnName"));
String toTbl = str(body.get("toTableName"));
String toCol = str(body.get("toColumnName"));
if (blank(relName) || blank(fromTbl) || blank(fromCol) || blank(toTbl) || blank(toCol)) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("필수 필드가 누락되었습니다. (relationshipName, fromTableName, fromColumnName, toTableName, toColumnName)"));
}
try {
Map<String, Object> created = dataflowService.insertTableRelationship(body, companyCode, userId);
return ResponseEntity.status(201)
.body(ApiResponse.success(created, "테이블 관계가 성공적으로 생성되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("테이블 관계 생성 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("테이블 관계 생성 중 오류가 발생했습니다."));
}
}
/**
* GET /api/dataflow/table-relationships
*/
@GetMapping("/table-relationships")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTableRelationshipList(
@RequestAttribute("companyCode") String companyCode) {
try {
return ResponseEntity.ok(ApiResponse.success(
dataflowService.getTableRelationshipList(companyCode),
"테이블 관계 목록을 성공적으로 조회했습니다."));
} catch (Exception e) {
log.error("테이블 관계 목록 조회 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("테이블 관계 목록 조회 중 오류가 발생했습니다."));
}
}
/**
* GET /api/dataflow/table-relationships/:relationshipId
*/
@GetMapping("/table-relationships/{relationshipId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTableRelationshipInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int relationshipId) {
try {
Map<String, Object> rel = dataflowService.getTableRelationshipInfo(relationshipId, companyCode);
if (rel == null) {
return ResponseEntity.status(404).body(ApiResponse.error("테이블 관계를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(rel, "테이블 관계를 성공적으로 조회했습니다."));
} catch (Exception e) {
log.error("테이블 관계 조회 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("테이블 관계 조회 중 오류가 발생했습니다."));
}
}
/**
* PUT /api/dataflow/table-relationships/:relationshipId
*/
@PutMapping("/table-relationships/{relationshipId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateTableRelationship(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable int relationshipId,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> updated = dataflowService.updateTableRelationship(
relationshipId, body, companyCode, userId);
if (updated == null) {
return ResponseEntity.status(404).body(ApiResponse.error("테이블 관계를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(updated, "테이블 관계가 성공적으로 수정되었습니다."));
} catch (Exception e) {
log.error("테이블 관계 수정 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("테이블 관계 수정 중 오류가 발생했습니다."));
}
}
/**
* DELETE /api/dataflow/table-relationships/:relationshipId
*/
@DeleteMapping("/table-relationships/{relationshipId}")
public ResponseEntity<ApiResponse<Void>> deleteTableRelationship(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int relationshipId) {
try {
boolean deleted = dataflowService.deleteTableRelationship(relationshipId, companyCode);
if (!deleted) {
return ResponseEntity.status(404).body(ApiResponse.error("테이블 관계를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "테이블 관계가 성공적으로 삭제되었습니다."));
} catch (Exception e) {
log.error("테이블 관계 삭제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("테이블 관계 삭제 중 오류가 발생했습니다."));
}
}
// ═══════════════════════════════════════════════════════════════════
// data-links
// ═══════════════════════════════════════════════════════════════════
/**
* POST /api/dataflow/data-links
*/
@PostMapping("/data-links")
public ResponseEntity<ApiResponse<Map<String, Object>>> createDataLink(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
if (body.get("relationshipId") == null || blank(str(body.get("fromTableName")))
|| blank(str(body.get("fromColumnName"))) || blank(str(body.get("toTableName")))
|| blank(str(body.get("toColumnName"))) || blank(str(body.get("connectionType")))) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("필수 필드가 누락되었습니다."));
}
try {
Map<String, Object> bridge = dataflowService.insertDataLink(body, companyCode, userId);
return ResponseEntity.status(201)
.body(ApiResponse.success(bridge, "데이터 연결이 성공적으로 생성되었습니다."));
} catch (Exception e) {
log.error("데이터 연결 생성 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("데이터 연결 생성 중 오류가 발생했습니다."));
}
}
/**
* GET /api/dataflow/data-links/relationship/:relationshipId
*/
@GetMapping("/data-links/relationship/{relationshipId}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDataLinkList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int relationshipId) {
try {
return ResponseEntity.ok(ApiResponse.success(
dataflowService.getDataLinkList(relationshipId, companyCode),
"연결된 데이터를 성공적으로 조회했습니다."));
} catch (Exception e) {
log.error("연결 데이터 조회 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("연결된 데이터 조회 중 오류가 발생했습니다."));
}
}
/**
* DELETE /api/dataflow/data-links/:bridgeId
*/
@DeleteMapping("/data-links/{bridgeId}")
public ResponseEntity<ApiResponse<Void>> deleteDataLink(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable int bridgeId) {
try {
dataflowService.deleteDataLink(bridgeId, companyCode, userId);
return ResponseEntity.ok(ApiResponse.success(null, "데이터 연결이 성공적으로 삭제되었습니다."));
} catch (Exception e) {
log.error("데이터 연결 삭제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("데이터 연결 삭제 중 오류가 발생했습니다."));
}
}
// ═══════════════════════════════════════════════════════════════════
// table-data
// ═══════════════════════════════════════════════════════════════════
/**
* GET /api/dataflow/table-data/:tableName
*/
@GetMapping("/table-data/{tableName}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTableData(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int limit,
@RequestParam(defaultValue = "") String search,
@RequestParam(defaultValue = "") String searchColumn) {
try {
Map<String, Object> result = dataflowService.getTableDataList(
tableName, page, limit, search, searchColumn, companyCode);
return ResponseEntity.ok(ApiResponse.success(result, "테이블 데이터를 성공적으로 조회했습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("테이블 데이터 조회 실패: {}", tableName, e);
return ResponseEntity.status(500).body(ApiResponse.error("테이블 데이터 조회 중 오류가 발생했습니다."));
}
}
// ═══════════════════════════════════════════════════════════════════
// diagrams
// ═══════════════════════════════════════════════════════════════════
/**
* GET /api/dataflow/diagrams
*/
@GetMapping("/diagrams")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDiagramList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "") String searchTerm) {
try {
return ResponseEntity.ok(ApiResponse.success(
dataflowService.getDiagramList(companyCode, page, size, searchTerm),
"관계도 목록을 성공적으로 조회했습니다."));
} catch (Exception e) {
log.error("관계도 목록 조회 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("관계도 목록 조회 중 오류가 발생했습니다."));
}
}
/**
* GET /api/dataflow/diagrams/name/:diagramName/relationships
* ※ /{diagramId}/relationships 보다 먼저 선언해야 "name" 세그먼트가 우선 매핑됨
*/
@GetMapping("/diagrams/name/{diagramName}/relationships")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDiagramRelationshipList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String diagramName) {
try {
return ResponseEntity.ok(ApiResponse.success(
dataflowService.getDiagramRelationshipList(
companyCode, java.net.URLDecoder.decode(diagramName, "UTF-8")),
"관계도 관계를 성공적으로 조회했습니다."));
} catch (Exception e) {
log.error("관계도 관계 조회 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("관계도 관계 조회 중 오류가 발생했습니다."));
}
}
/**
* GET /api/dataflow/diagrams/:diagramId/relationships
*/
@GetMapping("/diagrams/{diagramId}/relationships")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDiagramRelationshipListByDiagramId(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int diagramId) {
try {
return ResponseEntity.ok(ApiResponse.success(
dataflowService.getDiagramRelationshipListByDiagramId(companyCode, diagramId),
"관계도 관계 목록을 성공적으로 조회했습니다."));
} catch (Exception e) {
log.error("관계도 관계 조회 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("관계도 관계 조회에 실패했습니다."));
}
}
/**
* POST /api/dataflow/diagrams/:diagramName/copy
*/
@PostMapping("/diagrams/{diagramName}/copy")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyDiagram(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String diagramName) {
try {
String decoded = java.net.URLDecoder.decode(diagramName, "UTF-8");
String newDiagramName = dataflowService.copyDiagram(companyCode, decoded);
Map<String, Object> data = new HashMap<>();
data.put("newDiagramName", newDiagramName);
return ResponseEntity.ok(ApiResponse.success(data, "관계도가 성공적으로 복사되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("관계도 복사 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("관계도 복사에 실패했습니다."));
}
}
/**
* DELETE /api/dataflow/diagrams/:diagramName
*/
@DeleteMapping("/diagrams/{diagramName}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteDiagram(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String diagramName) {
try {
String decoded = java.net.URLDecoder.decode(diagramName, "UTF-8");
int deletedCount = dataflowService.deleteDiagram(companyCode, decoded);
Map<String, Object> data = new HashMap<>();
data.put("deletedCount", deletedCount);
return ResponseEntity.ok(ApiResponse.success(data, "관계도가 성공적으로 삭제되었습니다."));
} catch (Exception e) {
log.error("관계도 삭제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("관계도 삭제에 실패했습니다."));
}
}
/**
* GET /api/dataflow/relationships/:relationshipId/diagram
* (하위 호환성)
*/
@GetMapping("/relationships/{relationshipId}/diagram")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDiagramRelationshipListByRelationshipId(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int relationshipId) {
try {
return ResponseEntity.ok(ApiResponse.success(
dataflowService.getDiagramRelationshipListByRelationshipId(companyCode, relationshipId),
"관계도 관계 목록을 성공적으로 조회했습니다."));
} catch (Exception e) {
log.error("관계도 관계 조회 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("관계도 관계 조회에 실패했습니다."));
}
}
// ═══════════════════════════════════════════════════════════════════
// conditional connections (stub — conditionalConnectionController 미구현)
// ═══════════════════════════════════════════════════════════════════
/**
* POST /api/dataflow/diagrams/:diagramId/test-conditions
*/
@PostMapping("/diagrams/{diagramId}/test-conditions")
public ResponseEntity<ApiResponse<Map<String, Object>>> testConditionalConnection(
@PathVariable int diagramId,
@RequestBody(required = false) Map<String, Object> body) {
Map<String, Object> result = new HashMap<>();
result.put("diagramId", diagramId);
result.put("tested", true);
return ResponseEntity.ok(ApiResponse.success(result, "조건 테스트가 완료되었습니다."));
}
/**
* POST /api/dataflow/diagrams/:diagramId/execute-actions
*/
@PostMapping("/diagrams/{diagramId}/execute-actions")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeConditionalActions(
@PathVariable int diagramId,
@RequestBody(required = false) Map<String, Object> body) {
Map<String, Object> result = new HashMap<>();
result.put("diagramId", diagramId);
result.put("executed", true);
return ResponseEntity.ok(ApiResponse.success(result, "액션이 성공적으로 실행되었습니다."));
}
// ═══════════════════════════════════════════════════════════════════
// helpers
// ═══════════════════════════════════════════════════════════════════
private String str(Object val) { return val == null ? null : val.toString(); }
private boolean blank(String val) { return val == null || val.isBlank(); }
}
@@ -0,0 +1,231 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DataflowDiagramService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 데이터 흐름 관계도(Dataflow Diagram) API
*
* GET /api/dataflow-diagrams — 목록 조회 (페이지네이션)
* GET /api/dataflow-diagrams/:diagramId — 단건 조회
* POST /api/dataflow-diagrams — 생성
* PUT /api/dataflow-diagrams/:diagramId — 수정
* DELETE /api/dataflow-diagrams/:diagramId — 삭제
* POST /api/dataflow-diagrams/:diagramId/copy — 복제
*/
@RestController
@RequestMapping("/api/dataflow-diagrams")
@RequiredArgsConstructor
@Slf4j
public class DataflowDiagramController {
private final DataflowDiagramService service;
// ─────────────────────────────────────────────────────────
// GET /api/dataflow-diagrams
// ─────────────────────────────────────────────────────────
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getDataflowDiagrams(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getDataflowDiagrams(params)));
}
// ─────────────────────────────────────────────────────────
// GET /api/dataflow-diagrams/:diagramId
// ─────────────────────────────────────────────────────────
@GetMapping("/{diagramId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDataflowDiagramById(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("diagramId") String diagramIdStr) {
int diagramId = parseId(diagramIdStr);
if (diagramId < 0) {
return ResponseEntity.status(400)
.body(ApiResponse.error("유효하지 않은 관계도 ID입니다."));
}
Map<String, Object> diagram = service.getDataflowDiagramById(diagramId, companyCode);
if (diagram == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("관계도를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(diagram));
}
// ─────────────────────────────────────────────────────────
// POST /api/dataflow-diagrams
// ─────────────────────────────────────────────────────────
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createDataflowDiagram(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
if (body.get("diagram_name") == null || body.get("relationships") == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("관계도 이름과 관계 정보는 필수입니다."));
}
try {
Map<String, Object> created = service.createDataflowDiagram(body, companyCode, userId);
return ResponseEntity.status(201)
.body(ApiResponse.success(created, "관계도가 성공적으로 생성되었습니다."));
} catch (DataIntegrityViolationException e) {
if (isDuplicateNameError(e)) {
return ResponseEntity.status(409)
.body(ApiResponse.error("중복된 이름입니다."));
}
log.error("관계도 생성 실패 (제약 위반)", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("관계도 생성 중 오류가 발생했습니다."));
} catch (Exception e) {
log.error("관계도 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("관계도 생성 중 오류가 발생했습니다."));
}
}
// ─────────────────────────────────────────────────────────
// PUT /api/dataflow-diagrams/:diagramId
// ─────────────────────────────────────────────────────────
@PutMapping("/{diagramId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateDataflowDiagram(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("diagramId") String diagramIdStr,
@RequestBody Map<String, Object> body) {
int diagramId = parseId(diagramIdStr);
if (diagramId < 0) {
return ResponseEntity.status(400)
.body(ApiResponse.error("유효하지 않은 관계도 ID입니다."));
}
try {
Map<String, Object> updated =
service.updateDataflowDiagram(diagramId, body, companyCode, userId);
if (updated == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("관계도를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(updated, "관계도가 성공적으로 수정되었습니다."));
} catch (DataIntegrityViolationException e) {
if (isDuplicateNameError(e)) {
return ResponseEntity.status(409)
.body(ApiResponse.error("중복된 이름입니다."));
}
log.error("관계도 수정 실패 (제약 위반)", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("관계도 수정 중 오류가 발생했습니다."));
} catch (Exception e) {
log.error("관계도 수정 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("관계도 수정 중 오류가 발생했습니다."));
}
}
// ─────────────────────────────────────────────────────────
// DELETE /api/dataflow-diagrams/:diagramId
// ─────────────────────────────────────────────────────────
@DeleteMapping("/{diagramId}")
public ResponseEntity<ApiResponse<Void>> deleteDataflowDiagram(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("diagramId") String diagramIdStr) {
int diagramId = parseId(diagramIdStr);
if (diagramId < 0) {
return ResponseEntity.status(400)
.body(ApiResponse.error("유효하지 않은 관계도 ID입니다."));
}
try {
boolean deleted = service.deleteDataflowDiagram(diagramId, companyCode);
if (!deleted) {
return ResponseEntity.status(404)
.body(ApiResponse.error("관계도를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "관계도가 성공적으로 삭제되었습니다."));
} catch (Exception e) {
log.error("관계도 삭제 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("관계도 삭제 중 오류가 발생했습니다."));
}
}
// ─────────────────────────────────────────────────────────
// POST /api/dataflow-diagrams/:diagramId/copy
// ─────────────────────────────────────────────────────────
@PostMapping("/{diagramId}/copy")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyDataflowDiagram(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("diagramId") String diagramIdStr,
@RequestBody(required = false) Map<String, Object> body) {
int diagramId = parseId(diagramIdStr);
if (diagramId < 0) {
return ResponseEntity.status(400)
.body(ApiResponse.error("유효하지 않은 관계도 ID입니다."));
}
String newName = (body != null) ? (String) body.get("new_name") : null;
try {
Map<String, Object> copied =
service.copyDataflowDiagram(diagramId, companyCode, newName, userId);
if (copied == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("복제할 관계도를 찾을 수 없습니다."));
}
return ResponseEntity.status(201)
.body(ApiResponse.success(copied, "관계도가 성공적으로 복제되었습니다."));
} catch (Exception e) {
log.error("관계도 복제 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("관계도 복제 중 오류가 발생했습니다."));
}
}
// ─────────────────────────────────────────────────────────
// private helpers
// ─────────────────────────────────────────────────────────
/** Path variable ID 파싱 (유효하지 않으면 -1) */
private int parseId(String val) {
try {
int id = Integer.parseInt(val);
return id > 0 ? id : -1;
} catch (NumberFormatException e) {
return -1;
}
}
/** DataIntegrityViolationException 이 unique_diagram_name 위반인지 확인 */
private boolean isDuplicateNameError(DataIntegrityViolationException e) {
String msg = e.getMessage() != null ? e.getMessage().toLowerCase() : "";
Throwable cause = e.getCause();
String causeMsg = (cause != null && cause.getMessage() != null)
? cause.getMessage().toLowerCase() : "";
String combined = msg + " " + causeMsg;
return combined.contains("23505")
|| combined.contains("unique_diagram_name")
|| combined.contains("duplicate key")
|| combined.contains("unique constraint");
}
}
@@ -0,0 +1,60 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DataflowExecutionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 데이터플로우 실행 컨트롤러
*
* POST /api/dataflow/execute-data-action
* body: { tableName, data, actionType, connection? }
*/
@RestController
@RequestMapping("/api/dataflow")
@RequiredArgsConstructor
@Slf4j
public class DataflowExecutionController {
private final DataflowExecutionService service;
@PostMapping("/execute-data-action")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeDataAction(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
try {
body.put("companyCode", companyCode);
log.info("데이터 액션 실행 요청: action={}, table={}",
body.get("actionType"), body.get("tableName"));
Map<String, Object> result = service.executeDataAction(body);
return ResponseEntity.ok(
ApiResponse.success(result, "데이터 액션 실행 완료: " + body.get("actionType")));
} catch (IllegalArgumentException e) {
log.warn("데이터 액션 파라미터 오류: {}", e.getMessage());
return ResponseEntity.status(400)
.body(ApiResponse.error(e.getMessage()));
} catch (IllegalStateException e) {
// 중복 값 등 비즈니스 규칙 위반
log.warn("데이터 액션 비즈니스 오류: {}", e.getMessage());
return ResponseEntity.status(409)
.body(ApiResponse.error(e.getMessage()));
} catch (UnsupportedOperationException e) {
log.warn("지원하지 않는 액션: {}", e.getMessage());
return ResponseEntity.status(400)
.body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("데이터 액션 실행 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error("데이터 액션 실행 실패: " + e.getMessage()));
}
}
}
@@ -0,0 +1,117 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DbTypeCategoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/db-type-categories")
@RequiredArgsConstructor
@Slf4j
public class DbTypeCategoryController {
private final DbTypeCategoryService dbTypeCategoryService;
/**
* GET /api/db-type-categories
* 모든 DB 타입 카테고리 조회
*/
@GetMapping
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAllCategories() {
List<Map<String, Object>> categories = dbTypeCategoryService.getAllCategories();
return ResponseEntity.ok(ApiResponse.success(categories, "DB 타입 카테고리 목록을 조회했습니다."));
}
/**
* GET /api/db-type-categories/stats/connections
* DB 타입별 연결 통계 조회
* NOTE: /{typeCode} 보다 먼저 선언 — 리터럴 세그먼트 우선
*/
@GetMapping("/stats/connections")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getConnectionStatsByType() {
List<Map<String, Object>> stats = dbTypeCategoryService.getConnectionStatsByType();
return ResponseEntity.ok(ApiResponse.success(stats, "DB 타입별 연결 통계를 조회했습니다."));
}
/**
* GET /api/db-type-categories/{typeCode}
* 특정 DB 타입 카테고리 조회
*/
@GetMapping("/{typeCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCategoryByTypeCode(
@PathVariable String typeCode) {
Map<String, Object> category = dbTypeCategoryService.getCategoryByTypeCode(typeCode);
if (category == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("해당 DB 타입 카테고리를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(category, "DB 타입 카테고리를 조회했습니다."));
}
/**
* POST /api/db-type-categories
* DB 타입 카테고리 생성
*/
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createCategory(
@RequestBody Map<String, Object> body) {
String typeCode = (String) body.get("type_code");
String displayName = (String) body.get("display_name");
if (typeCode == null || typeCode.isBlank() || displayName == null || displayName.isBlank()) {
return ResponseEntity.status(400)
.body(ApiResponse.error("DB 타입 코드와 표시명은 필수입니다."));
}
try {
Map<String, Object> created = dbTypeCategoryService.createCategory(body);
return ResponseEntity.status(201).body(ApiResponse.success(created, "DB 타입 카테고리가 생성되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
/**
* POST /api/db-type-categories/initialize
* 기본 DB 타입 카테고리 초기화
* NOTE: POST /{typeCode} 충돌 없음 — 경로가 완전히 다름
*/
@PostMapping("/initialize")
public ResponseEntity<ApiResponse<Void>> initializeDefaultCategories() {
dbTypeCategoryService.initializeDefaultCategories();
return ResponseEntity.ok(ApiResponse.success(null, "기본 DB 타입 카테고리가 초기화되었습니다."));
}
/**
* PUT /api/db-type-categories/{typeCode}
* DB 타입 카테고리 수정
*/
@PutMapping("/{typeCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateCategory(
@PathVariable String typeCode,
@RequestBody Map<String, Object> body) {
Map<String, Object> updated = dbTypeCategoryService.updateCategory(typeCode, body);
return ResponseEntity.ok(ApiResponse.success(updated, "DB 타입 카테고리가 수정되었습니다."));
}
/**
* DELETE /api/db-type-categories/{typeCode}
* DB 타입 카테고리 삭제 (비활성화)
*/
@DeleteMapping("/{typeCode}")
public ResponseEntity<ApiResponse<Void>> deleteCategory(
@PathVariable String typeCode) {
try {
dbTypeCategoryService.deleteCategory(typeCode);
return ResponseEntity.ok(ApiResponse.success(null, "DB 타입 카테고리가 삭제되었습니다."));
} catch (IllegalStateException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
}
@@ -0,0 +1,298 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DdlService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/ddl")
@RequiredArgsConstructor
@Slf4j
public class DdlController {
private final DdlService ddlService;
// ─────────────────────────────────────────────────────────────────────────
// DDL 실행 (SUPER_ADMIN 전용)
// ─────────────────────────────────────────────────────────────────────────
/**
* POST /api/ddl/tables - 새 테이블 생성
*/
@PostMapping("/tables")
public ResponseEntity<ApiResponse<?>> createTable(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
if (!isSuperAdmin(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
}
String tableName = (String) body.get("tableName");
@SuppressWarnings("unchecked")
List<Map<String, Object>> columns = (List<Map<String, Object>>) body.get("columns");
String description = (String) body.get("description");
if (tableName == null || tableName.isBlank() || columns == null || columns.isEmpty()) {
return ResponseEntity.status(400)
.body(ApiResponse.error("테이블명과 최소 1개의 컬럼이 필요합니다."));
}
Map<String, Object> result = ddlService.createTable(tableName, columns, companyCode, userId, description);
if (Boolean.TRUE.equals(result.get("success"))) {
return ResponseEntity.ok(ApiResponse.success(Map.of(
"tableName", result.get("tableName"),
"columnCount", result.get("columnCount"),
"executedQuery", result.get("executedQuery")
), (String) result.get("message")));
}
return ResponseEntity.status(400).body(ApiResponse.error((String) result.get("message")));
}
/**
* POST /api/ddl/tables/{tableName}/columns - 컬럼 추가
*/
@PostMapping("/tables/{tableName}/columns")
public ResponseEntity<ApiResponse<?>> addColumn(
@PathVariable String tableName,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
if (!isSuperAdmin(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
}
@SuppressWarnings("unchecked")
Map<String, Object> column = (Map<String, Object>) body.get("column");
if (column == null || column.get("name") == null
|| column.get("name").toString().isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("컬럼명이 필요합니다."));
}
Map<String, Object> result = ddlService.addColumn(tableName, column, companyCode, userId);
if (Boolean.TRUE.equals(result.get("success"))) {
return ResponseEntity.ok(ApiResponse.success(Map.of(
"tableName", result.get("tableName"),
"columnName", result.get("columnName"),
"executedQuery", result.get("executedQuery")
), (String) result.get("message")));
}
return ResponseEntity.status(400).body(ApiResponse.error((String) result.get("message")));
}
/**
* DELETE /api/ddl/tables/{tableName} - 테이블 삭제
*/
@DeleteMapping("/tables/{tableName}")
public ResponseEntity<ApiResponse<?>> dropTable(
@PathVariable String tableName,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
if (!isSuperAdmin(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
}
Map<String, Object> result = ddlService.dropTable(tableName, companyCode, userId);
if (Boolean.TRUE.equals(result.get("success"))) {
return ResponseEntity.ok(ApiResponse.success(Map.of(
"tableName", result.get("tableName"),
"executedQuery", result.get("executedQuery")
), (String) result.get("message")));
}
return ResponseEntity.status(400).body(ApiResponse.error((String) result.get("message")));
}
/**
* POST /api/ddl/validate/table - 테이블 생성 사전 검증 (실제 생성 없음)
*/
@PostMapping("/validate/table")
public ResponseEntity<ApiResponse<?>> validateTableCreation(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
if (!isSuperAdmin(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
}
String tableName = (String) body.get("tableName");
@SuppressWarnings("unchecked")
List<Map<String, Object>> columns = (List<Map<String, Object>>) body.get("columns");
if (tableName == null || columns == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("테이블명과 컬럼 정보가 필요합니다."));
}
Map<String, Object> result = ddlService.validateTableCreation(tableName, columns);
return ResponseEntity.ok(ApiResponse.success(result, "검증 완료"));
}
// ─────────────────────────────────────────────────────────────────────────
// 로그 및 모니터링
// ─────────────────────────────────────────────────────────────────────────
/**
* GET /api/ddl/logs - DDL 실행 로그 조회
*/
@GetMapping("/logs")
public ResponseEntity<ApiResponse<?>> getDdlLogs(
@RequestAttribute("companyCode") String companyCode,
@RequestParam(required = false, defaultValue = "50") int limit,
@RequestParam(required = false) String userId,
@RequestParam(required = false) String ddlType) {
if (!isSuperAdmin(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
}
List<Map<String, Object>> logs = ddlService.getDdlLogs(limit, userId, ddlType);
return ResponseEntity.ok(ApiResponse.success(
Map.of("logs", logs, "total", logs.size()), "DDL 로그 조회 성공"));
}
/**
* GET /api/ddl/statistics - DDL 실행 통계 조회
*/
@GetMapping("/statistics")
public ResponseEntity<ApiResponse<?>> getDdlStatistics(
@RequestAttribute("companyCode") String companyCode,
@RequestParam(required = false) String fromDate,
@RequestParam(required = false) String toDate) {
if (!isSuperAdmin(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
}
Map<String, Object> statistics = ddlService.getDdlStatistics(fromDate, toDate);
return ResponseEntity.ok(ApiResponse.success(statistics, "DDL 통계 조회 성공"));
}
/**
* GET /api/ddl/tables/{tableName}/history - 테이블 DDL 히스토리 조회
*/
@GetMapping("/tables/{tableName}/history")
public ResponseEntity<ApiResponse<?>> getTableDdlHistory(
@PathVariable String tableName,
@RequestAttribute("companyCode") String companyCode) {
if (!isSuperAdmin(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
}
List<Map<String, Object>> history = ddlService.getTableDdlHistory(tableName);
return ResponseEntity.ok(ApiResponse.success(
Map.of("tableName", tableName, "history", history, "total", history.size()),
"테이블 DDL 히스토리 조회 성공"));
}
/**
* GET /api/ddl/tables/{tableName}/info - 테이블 정보 조회
*/
@GetMapping("/tables/{tableName}/info")
public ResponseEntity<ApiResponse<?>> getTableInfo(
@PathVariable String tableName,
@RequestAttribute("companyCode") String companyCode) {
if (!isSuperAdmin(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
}
Map<String, Object> tableInfo = ddlService.getTableInfo(tableName);
if (tableInfo == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("테이블 '" + tableName + "'을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(tableInfo, "테이블 정보 조회 성공"));
}
// ─────────────────────────────────────────────────────────────────────────
// 시스템 관리
// ─────────────────────────────────────────────────────────────────────────
/**
* DELETE /api/ddl/logs/cleanup - 오래된 DDL 로그 정리
* NOTE: /logs/cleanup 은 /logs/{id} 보다 우선 매칭 (리터럴 세그먼트 우선)
*/
@DeleteMapping("/logs/cleanup")
public ResponseEntity<ApiResponse<?>> cleanupOldLogs(
@RequestAttribute("companyCode") String companyCode,
@RequestParam(required = false, defaultValue = "90") int retentionDays) {
if (!isSuperAdmin(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
}
int deletedCount = ddlService.cleanupOldLogs(retentionDays);
return ResponseEntity.ok(ApiResponse.success(
Map.of("deletedCount", deletedCount, "retentionDays", retentionDays),
deletedCount + "개의 오래된 DDL 로그가 삭제되었습니다."));
}
/**
* GET /api/ddl/info - DDL 서비스 정보
*/
@GetMapping("/info")
public ResponseEntity<ApiResponse<?>> getInfo(
@RequestAttribute("companyCode") String companyCode) {
if (!isSuperAdmin(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
}
return ResponseEntity.ok(ApiResponse.success(Map.of(
"service", "DDL Execution Service",
"version", "1.0.0",
"description", "PostgreSQL 테이블 및 컬럼 동적 생성 서비스",
"requirements", Map.of(
"authentication", "Bearer Token 필요",
"authorization", "회사코드 '*'인 SUPER_ADMIN만 접근 가능",
"safety", "모든 DDL 실행은 안전성 검증 후 수행",
"logging", "모든 DDL 실행은 감사 로그에 기록"
),
"endpoints", Map.of(
"createTable", "POST /api/ddl/tables",
"addColumn", "POST /api/ddl/tables/{tableName}/columns",
"dropTable", "DELETE /api/ddl/tables/{tableName}",
"validate", "POST /api/ddl/validate/table",
"logs", "GET /api/ddl/logs",
"statistics", "GET /api/ddl/statistics",
"history", "GET /api/ddl/tables/{tableName}/history",
"tableInfo", "GET /api/ddl/tables/{tableName}/info",
"cleanup", "DELETE /api/ddl/logs/cleanup"
)
), "DDL 서비스 정보"));
}
/**
* GET /api/ddl/health - 헬스체크 (인증만 확인, SUPER_ADMIN 불필요)
*/
@GetMapping("/health")
public ResponseEntity<ApiResponse<?>> health() {
return ResponseEntity.ok(ApiResponse.success(Map.of(
"status", "healthy",
"timestamp", Instant.now().toString(),
"checks", Map.of("service", "operational")
), "DDL 서비스 정상"));
}
// ─────────────────────────────────────────────────────────────────────────
// 내부 유틸
// ─────────────────────────────────────────────────────────────────────────
private boolean isSuperAdmin(String companyCode) {
return "*".equals(companyCode);
}
}
@@ -0,0 +1,66 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DeliveryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/delivery")
@RequiredArgsConstructor
@Slf4j
public class DeliveryController {
private final DeliveryService deliveryService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDeliveryList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(deliveryService.getDeliveryList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDeliveryInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(deliveryService.getDeliveryInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertDelivery(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(deliveryService.insertDelivery(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateDelivery(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(deliveryService.updateDelivery(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteDelivery(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(deliveryService.deleteDelivery(params)));
}
}
@@ -0,0 +1,245 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DepartmentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/departments")
@RequiredArgsConstructor
@Slf4j
public class DepartmentController {
private final DepartmentService departmentService;
/**
* 부서 목록 조회 (회사별)
* GET /api/departments/companies/{companyCode}/departments
*/
@GetMapping("/companies/{companyCode}/departments")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDepartments(
@PathVariable String companyCode,
@RequestAttribute("companyCode") String userCompanyCode) {
if (!isSuperAdmin(userCompanyCode) && !userCompanyCode.equals(companyCode)) {
return ResponseEntity.status(403)
.body(ApiResponse.error("해당 회사의 부서를 조회할 권한이 없습니다."));
}
List<Map<String, Object>> departments = departmentService.getDepartments(companyCode);
return ResponseEntity.ok(ApiResponse.success(departments, "부서 목록 조회 성공"));
}
/**
* 부서 상세 조회
* GET /api/departments/{deptCode}
*/
@GetMapping("/{deptCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDepartment(
@PathVariable String deptCode) {
Map<String, Object> department = departmentService.getDepartment(deptCode);
if (department == null) {
return ResponseEntity.status(404).body(ApiResponse.error("부서를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(department, "부서 조회 성공"));
}
/**
* 부서 생성
* POST /api/departments/companies/{companyCode}/departments
*/
@PostMapping("/companies/{companyCode}/departments")
public ResponseEntity<ApiResponse<Map<String, Object>>> createDepartment(
@PathVariable String companyCode,
@RequestAttribute("companyCode") String userCompanyCode,
@RequestAttribute("role") String role,
@RequestBody Map<String, Object> body) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
if (!isSuperAdmin(userCompanyCode) && !userCompanyCode.equals(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("해당 회사의 부서를 생성할 권한이 없습니다."));
}
try {
Map<String, Object> created = departmentService.createDepartment(companyCode, body);
return ResponseEntity.status(201).body(ApiResponse.success(created, "부서가 생성되었습니다."));
} catch (DepartmentService.DuplicateDeptNameException e) {
return ResponseEntity.status(409).body(ApiResponse.error(e.getMessage()));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
/**
* 부서 수정
* PUT /api/departments/{deptCode}
*/
@PutMapping("/{deptCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateDepartment(
@PathVariable String deptCode,
@RequestAttribute("role") String role,
@RequestBody Map<String, Object> body) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
try {
Map<String, Object> updated = departmentService.updateDepartment(deptCode, body);
if (updated == null) {
return ResponseEntity.status(404).body(ApiResponse.error("부서를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(updated, "부서가 수정되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
/**
* 부서 삭제
* DELETE /api/departments/{deptCode}
*/
@DeleteMapping("/{deptCode}")
public ResponseEntity<ApiResponse<Void>> deleteDepartment(
@PathVariable String deptCode,
@RequestAttribute("role") String role) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
try {
int memberCount = departmentService.deleteDepartment(deptCode);
if (memberCount == -1) {
return ResponseEntity.status(404).body(ApiResponse.error("부서를 찾을 수 없습니다."));
}
String message = memberCount > 0
? "부서가 삭제되었습니다. (부서원 " + memberCount + "명 제외됨)"
: "부서가 삭제되었습니다.";
return ResponseEntity.ok(ApiResponse.success(null, message));
} catch (IllegalStateException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
/**
* 부서원 목록 조회
* GET /api/departments/{deptCode}/members
*/
@GetMapping("/{deptCode}/members")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDeptMembers(
@PathVariable String deptCode) {
List<Map<String, Object>> members = departmentService.getDeptMembers(deptCode);
return ResponseEntity.ok(ApiResponse.success(members, "부서원 목록 조회 성공"));
}
/**
* 사용자 검색 (부서원 추가용)
* GET /api/departments/companies/{companyCode}/users/search
*/
@GetMapping("/companies/{companyCode}/users/search")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> searchUsers(
@PathVariable String companyCode,
@RequestParam(required = false) String search) {
if (search == null || search.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("검색어를 입력해주세요."));
}
List<Map<String, Object>> users = departmentService.searchUsers(companyCode, search);
return ResponseEntity.ok(ApiResponse.success(users, "사용자 검색 성공"));
}
/**
* 부서원 추가
* POST /api/departments/{deptCode}/members
*/
@PostMapping("/{deptCode}/members")
public ResponseEntity<ApiResponse<Void>> addDeptMember(
@PathVariable String deptCode,
@RequestAttribute("role") String role,
@RequestBody Map<String, Object> body) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
// 프론트엔드는 snake_case(user_id)로 전송 (Node.js 호환)
Object userIdObj = body.get("user_id");
if (userIdObj == null) userIdObj = body.get("userId");
if (userIdObj == null || userIdObj.toString().isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("사용자 ID를 입력해주세요."));
}
String userId = userIdObj.toString();
try {
departmentService.addDeptMember(deptCode, userId);
return ResponseEntity.status(201).body(ApiResponse.success(null, "부서원이 추가되었습니다."));
} catch (DepartmentService.DuplicateMemberException e) {
return ResponseEntity.status(409).body(ApiResponse.error(e.getMessage()));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
/**
* 부서원 제거
* DELETE /api/departments/{deptCode}/members/{userId}
*/
@DeleteMapping("/{deptCode}/members/{userId}")
public ResponseEntity<ApiResponse<Void>> removeDeptMember(
@PathVariable String deptCode,
@PathVariable String userId,
@RequestAttribute("role") String role) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
boolean removed = departmentService.removeDeptMember(deptCode, userId);
if (!removed) {
return ResponseEntity.status(404).body(ApiResponse.error("해당 부서원을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "부서원이 제거되었습니다."));
}
/**
* 주 부서 설정
* PUT /api/departments/{deptCode}/members/{userId}/primary
*/
@PutMapping("/{deptCode}/members/{userId}/primary")
public ResponseEntity<ApiResponse<Void>> setPrimaryDept(
@PathVariable String deptCode,
@PathVariable String userId,
@RequestAttribute("role") String role) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
departmentService.setPrimaryDept(deptCode, userId);
return ResponseEntity.ok(ApiResponse.success(null, "주 부서가 설정되었습니다."));
}
// ──────────────────────────────────────────────────
// 내부 유틸
// ──────────────────────────────────────────────────
private boolean isAdmin(String role) {
return isSuperAdmin(role) || "COMPANY_ADMIN".equals(role);
}
private boolean isSuperAdmin(String companyCodeOrRole) {
return "*".equals(companyCodeOrRole) || "SUPER_ADMIN".equals(companyCodeOrRole);
}
}
@@ -0,0 +1,330 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DesignService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/design")
@RequiredArgsConstructor
@Slf4j
public class DesignController {
private final DesignService designService;
// ============================================
// 설계의뢰 (Design Request)
// ============================================
@GetMapping("/requests")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDesignRequestList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(designService.getDesignRequestList(params)));
}
@GetMapping("/requests/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDesignRequestDetail(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String id) {
Map<String, Object> result = designService.getDesignRequestDetail(id, companyCode);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("의뢰를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping("/requests")
public ResponseEntity<ApiResponse<Map<String, Object>>> createDesignRequest(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(designService.createDesignRequest(body, companyCode, userId)));
}
@PutMapping("/requests/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateDesignRequest(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String id,
@RequestBody Map<String, Object> body) {
Map<String, Object> result = designService.updateDesignRequest(id, body, companyCode, userId);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("의뢰를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@DeleteMapping("/requests/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteDesignRequest(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String id) {
boolean deleted = designService.deleteDesignRequest(id, companyCode);
if (!deleted) return ResponseEntity.status(404).body(ApiResponse.error("의뢰를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null));
}
@PostMapping("/requests/{id}/history")
public ResponseEntity<ApiResponse<Map<String, Object>>> addRequestHistory(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String id,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(designService.addRequestHistory(id, body, companyCode, userId)));
}
// ============================================
// 설계 프로젝트
// ============================================
@GetMapping("/projects")
public ResponseEntity<ApiResponse<Map<String, Object>>> getProjectList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(designService.getProjectList(params)));
}
@GetMapping("/projects/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getProjectDetail(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String id) {
Map<String, Object> result = designService.getProjectDetail(id, companyCode);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("프로젝트를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping("/projects")
public ResponseEntity<ApiResponse<Map<String, Object>>> createProject(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(designService.createProject(body, companyCode, userId)));
}
@PutMapping("/projects/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateProject(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String id,
@RequestBody Map<String, Object> body) {
Map<String, Object> result = designService.updateProject(id, body, companyCode);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("프로젝트를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@DeleteMapping("/projects/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteProject(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String id) {
boolean deleted = designService.deleteProject(id, companyCode);
if (!deleted) return ResponseEntity.status(404).body(ApiResponse.error("프로젝트를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null));
}
// ============================================
// 프로젝트 태스크
// ============================================
@GetMapping("/projects/{projectId}/tasks")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTasksByProject(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String projectId) {
return ResponseEntity.ok(ApiResponse.success(designService.getTasksByProject(projectId, companyCode)));
}
@PostMapping("/projects/{projectId}/tasks")
public ResponseEntity<ApiResponse<Map<String, Object>>> createTask(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String projectId,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(designService.createTask(projectId, body, companyCode, userId)));
}
@PutMapping("/tasks/{taskId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateTask(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String taskId,
@RequestBody Map<String, Object> body) {
Map<String, Object> result = designService.updateTask(taskId, body, companyCode);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("태스크를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@DeleteMapping("/tasks/{taskId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteTask(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String taskId) {
boolean deleted = designService.deleteTask(taskId, companyCode);
if (!deleted) return ResponseEntity.status(404).body(ApiResponse.error("태스크를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null));
}
// ============================================
// 작업일지
// ============================================
@GetMapping("/tasks/{taskId}/work-logs")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getWorkLogsByTask(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String taskId) {
return ResponseEntity.ok(ApiResponse.success(designService.getWorkLogsByTask(taskId, companyCode)));
}
@PostMapping("/tasks/{taskId}/work-logs")
public ResponseEntity<ApiResponse<Map<String, Object>>> createWorkLog(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String taskId,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(designService.createWorkLog(taskId, body, companyCode, userId)));
}
@DeleteMapping("/work-logs/{workLogId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteWorkLog(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String workLogId) {
boolean deleted = designService.deleteWorkLog(workLogId, companyCode);
if (!deleted) return ResponseEntity.status(404).body(ApiResponse.error("작업일지를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null));
}
// ============================================
// 태스크 하위항목
// ============================================
@PostMapping("/tasks/{taskId}/sub-items")
public ResponseEntity<ApiResponse<Map<String, Object>>> createSubItem(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String taskId,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(designService.createSubItem(taskId, body, companyCode, userId)));
}
@PutMapping("/sub-items/{subItemId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateSubItem(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String subItemId,
@RequestBody Map<String, Object> body) {
Map<String, Object> result = designService.updateSubItem(subItemId, body, companyCode);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("하위항목을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@DeleteMapping("/sub-items/{subItemId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteSubItem(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String subItemId) {
boolean deleted = designService.deleteSubItem(subItemId, companyCode);
if (!deleted) return ResponseEntity.status(404).body(ApiResponse.error("하위항목을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null));
}
// ============================================
// 태스크 이슈
// ============================================
@PostMapping("/tasks/{taskId}/issues")
public ResponseEntity<ApiResponse<Map<String, Object>>> createIssue(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String taskId,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(designService.createIssue(taskId, body, companyCode, userId)));
}
@PutMapping("/issues/{issueId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateIssue(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String issueId,
@RequestBody Map<String, Object> body) {
Map<String, Object> result = designService.updateIssue(issueId, body, companyCode);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("이슈를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
// ============================================
// ECN (설변통보)
// ============================================
@GetMapping("/ecn")
public ResponseEntity<ApiResponse<Map<String, Object>>> getEcnList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(designService.getEcnList(params)));
}
@PostMapping("/ecn")
public ResponseEntity<ApiResponse<Map<String, Object>>> createEcn(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(designService.createEcn(body, companyCode, userId)));
}
@PutMapping("/ecn/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateEcn(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String id,
@RequestBody Map<String, Object> body) {
Map<String, Object> result = designService.updateEcn(id, body, companyCode, userId);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("ECN을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@DeleteMapping("/ecn/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteEcn(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String id) {
boolean deleted = designService.deleteEcn(id, companyCode);
if (!deleted) return ResponseEntity.status(404).body(ApiResponse.error("ECN을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null));
}
// ============================================
// 나의 업무
// ============================================
@GetMapping("/my-work")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMyWork(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userName", required = false) String userName,
@RequestParam Map<String, Object> params) {
return ResponseEntity.ok(ApiResponse.success(designService.getMyWork(params, companyCode, userName)));
}
// ============================================
// 구매요청 / 협업요청
// ============================================
@PostMapping("/work-logs/{workLogId}/purchase-reqs")
public ResponseEntity<ApiResponse<Map<String, Object>>> createPurchaseReq(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String workLogId,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(designService.createPurchaseReq(workLogId, body, companyCode, userId)));
}
@PostMapping("/work-logs/{workLogId}/coop-reqs")
public ResponseEntity<ApiResponse<Map<String, Object>>> createCoopReq(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String workLogId,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(designService.createCoopReq(workLogId, body, companyCode, userId)));
}
@PostMapping("/coop-reqs/{coopReqId}/responses")
public ResponseEntity<ApiResponse<Map<String, Object>>> addCoopResponse(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String coopReqId,
@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(ApiResponse.success(designService.addCoopResponse(coopReqId, body, companyCode, userId)));
}
}
@@ -0,0 +1,229 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DigitalTwinService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/digital-twin")
@RequiredArgsConstructor
@Slf4j
public class DigitalTwinController {
private final DigitalTwinService digitalTwinService;
// ========== 레이아웃 관리 API ==========
/** GET /layouts → 레이아웃 목록 */
@GetMapping("/layouts")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDigitalTwinLayoutList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(digitalTwinService.getDigitalTwinLayoutList(params)));
}
/** GET /layouts/:id → 레이아웃 상세 (객체 포함) */
@GetMapping("/layouts/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDigitalTwinLayoutInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
Map<String, Object> result = digitalTwinService.getDigitalTwinLayoutInfo(params);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("레이아웃을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
/** POST /layouts → 레이아웃 생성 */
@PostMapping("/layouts")
public ResponseEntity<ApiResponse<Map<String, Object>>> createDigitalTwinLayout(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("createdBy", userId);
Map<String, Object> result = digitalTwinService.createDigitalTwinLayout(body);
return ResponseEntity.status(201).body(ApiResponse.success(result));
}
/** PUT /layouts/:id → 레이아웃 수정 */
@PutMapping("/layouts/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateDigitalTwinLayout(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("updatedBy", userId);
body.put("id", id);
Map<String, Object> result = digitalTwinService.updateDigitalTwinLayout(body);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("레이아웃을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
/** DELETE /layouts/:id → 레이아웃 삭제 */
@DeleteMapping("/layouts/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteDigitalTwinLayout(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
boolean deleted = digitalTwinService.deleteDigitalTwinLayout(params);
if (!deleted) {
return ResponseEntity.status(404).body(ApiResponse.error("레이아웃을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "레이아웃이 삭제되었습니다."));
}
// ========== 매핑 템플릿 API ==========
/** GET /mapping-templates → 매핑 템플릿 목록 */
@GetMapping("/mapping-templates")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDigitalTwinTemplateList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(digitalTwinService.getDigitalTwinTemplateList(params)));
}
/** GET /mapping-templates/:id → 매핑 템플릿 상세 */
@GetMapping("/mapping-templates/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDigitalTwinTemplateInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
Map<String, Object> result = digitalTwinService.getDigitalTwinTemplateInfo(params);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("매핑 템플릿을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
/** POST /mapping-templates → 매핑 템플릿 생성 */
@PostMapping("/mapping-templates")
public ResponseEntity<ApiResponse<Map<String, Object>>> createDigitalTwinTemplate(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
if (body.get("name") == null || body.get("externalDbConnectionId") == null || body.get("config") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("필수 필드가 누락되었습니다. (name, externalDbConnectionId, config)"));
}
body.put("companyCode", companyCode);
body.put("createdBy", userId);
Map<String, Object> result = digitalTwinService.createDigitalTwinTemplate(body);
return ResponseEntity.status(201).body(ApiResponse.success(result));
}
// ========== 외부 DB 데이터 조회 API ==========
/** POST /data/hierarchy → 전체 계층 데이터 조회 */
@PostMapping("/data/hierarchy")
public ResponseEntity<ApiResponse<Map<String, Object>>> getHierarchyData(
@RequestBody Map<String, Object> body) {
if (body.get("externalDbConnectionId") == null || body.get("hierarchyConfig") == null) {
return ResponseEntity.status(400).body(
ApiResponse.error("외부 DB 연결 ID와 계층 구조 설정이 필요합니다."));
}
Map<String, Object> result = digitalTwinService.getHierarchyData(body);
return ResponseEntity.ok(ApiResponse.success(result));
}
/** POST /data/children → 특정 부모의 하위 데이터 조회 */
@PostMapping("/data/children")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getChildrenData(
@RequestBody Map<String, Object> body) {
if (body.get("externalDbConnectionId") == null || body.get("hierarchyConfig") == null
|| body.get("parentLevel") == null || body.get("parentKey") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("필수 파라미터가 누락되었습니다."));
}
List<Map<String, Object>> result = digitalTwinService.getChildrenData(body);
return ResponseEntity.ok(ApiResponse.success(result));
}
/** GET /data/tables/:connectionId → 테이블 목록 */
@GetMapping("/data/tables/{connectionId}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getExternalTables(
@PathVariable Long connectionId) {
List<Map<String, Object>> result = digitalTwinService.getExternalTables(connectionId);
return ResponseEntity.ok(ApiResponse.success(result));
}
/** GET /data/table-preview/:connectionId/:tableName → 테이블 미리보기 */
@GetMapping("/data/table-preview/{connectionId}/{tableName}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTablePreview(
@PathVariable Long connectionId,
@PathVariable String tableName) {
List<Map<String, Object>> result = digitalTwinService.getTablePreview(connectionId, tableName);
return ResponseEntity.ok(ApiResponse.success(result));
}
/** GET /data/warehouses → 창고 목록 (레거시) */
@GetMapping("/data/warehouses")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getWarehouses(
@RequestParam Long externalDbConnectionId,
@RequestParam String tableName) {
if (externalDbConnectionId == null) {
return ResponseEntity.status(400).body(ApiResponse.error("외부 DB 연결 ID가 필요합니다."));
}
List<Map<String, Object>> result = digitalTwinService.getWarehouses(externalDbConnectionId, tableName);
return ResponseEntity.ok(ApiResponse.success(result));
}
/** GET /data/areas → 구역 목록 (레거시) */
@GetMapping("/data/areas")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAreas(
@RequestParam Long externalDbConnectionId,
@RequestParam String warehouseKey,
@RequestParam String tableName) {
List<Map<String, Object>> result = digitalTwinService.getAreas(externalDbConnectionId, tableName, warehouseKey);
return ResponseEntity.ok(ApiResponse.success(result));
}
/** GET /data/locations → 위치 목록 (레거시) */
@GetMapping("/data/locations")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getLocations(
@RequestParam Long externalDbConnectionId,
@RequestParam String areaKey,
@RequestParam String tableName) {
List<Map<String, Object>> result = digitalTwinService.getLocations(externalDbConnectionId, tableName, areaKey);
return ResponseEntity.ok(ApiResponse.success(result));
}
/** GET /data/materials → 자재 목록 (레거시) */
@GetMapping("/data/materials")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMaterials(
@RequestParam Long externalDbConnectionId,
@RequestParam String locaKey,
@RequestParam String tableName,
@RequestParam String locationKeyColumn,
@RequestParam(required = false) String layerColumn) {
List<Map<String, Object>> result = digitalTwinService.getMaterials(
externalDbConnectionId, tableName, locaKey, locationKeyColumn, layerColumn);
return ResponseEntity.ok(ApiResponse.success(result));
}
/** POST /data/material-counts → 자재 개수 (여러 Location, 레거시) */
@PostMapping("/data/material-counts")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMaterialCounts(
@RequestBody Map<String, Object> body) {
if (body.get("externalDbConnectionId") == null || body.get("locationKeys") == null
|| body.get("tableName") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("필수 파라미터가 누락되었습니다."));
}
List<Map<String, Object>> result = digitalTwinService.getMaterialCounts(body);
return ResponseEntity.ok(ApiResponse.success(result));
}
}
@@ -0,0 +1,125 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DriverService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/driver")
@RequiredArgsConstructor
@Slf4j
public class DriverController {
private final DriverService driverService;
/** GET /list → 운전자 목록 (admin용 / api_test 호환) */
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDriverList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(driverService.getDriverList(params)));
}
/** GET /profile → 운전자 프로필 조회 */
@GetMapping("/profile")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDriverProfile(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId) {
if (userId == null) {
return ResponseEntity.status(401).body(ApiResponse.error("인증이 필요합니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("userId", userId);
Map<String, Object> result = driverService.getDriverProfile(params);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("사용자를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
/** PUT /profile → 운전자 프로필 수정 */
@PutMapping("/profile")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateDriverProfile(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
if (userId == null) {
return ResponseEntity.status(401).body(ApiResponse.error("인증이 필요합니다."));
}
body.put("companyCode", companyCode);
body.put("userId", userId);
driverService.updateDriverProfile(body);
return ResponseEntity.ok(ApiResponse.success(null, "프로필이 수정되었습니다."));
}
/** PUT /status → 차량 상태 변경 (off: 대기, maintenance: 정비) */
@PutMapping("/status")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateDriverStatus(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
if (userId == null) {
return ResponseEntity.status(401).body(ApiResponse.error("인증이 필요합니다."));
}
String status = body.get("status") != null ? String.valueOf(body.get("status")) : null;
if (status == null || (!status.equals("off") && !status.equals("maintenance"))) {
return ResponseEntity.status(400).body(
ApiResponse.error("유효하지 않은 상태값입니다. (off: 대기, maintenance: 정비)"));
}
body.put("userId", userId);
driverService.updateDriverStatus(body);
String msg = "off".equals(status) ? "차량 상태가 대기로 변경되었습니다." : "차량 상태가 정비로 변경되었습니다.";
return ResponseEntity.ok(ApiResponse.success(null, msg));
}
/** DELETE /vehicle → 차량 삭제 (기록 보존) */
@DeleteMapping("/vehicle")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteDriverVehicle(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId) {
if (userId == null) {
return ResponseEntity.status(401).body(ApiResponse.error("인증이 필요합니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("userId", userId);
driverService.deleteDriverVehicle(params);
return ResponseEntity.ok(ApiResponse.success(null, "차량이 삭제되었습니다."));
}
/** POST /vehicle → 새 차량 등록 */
@PostMapping("/vehicle")
public ResponseEntity<ApiResponse<Map<String, Object>>> registerDriverVehicle(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
if (userId == null) {
return ResponseEntity.status(401).body(ApiResponse.error("인증이 필요합니다."));
}
if (body.get("vehicleNumber") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("차량번호는 필수입니다."));
}
body.put("companyCode", companyCode);
body.put("userId", userId);
driverService.registerDriverVehicle(body);
return ResponseEntity.ok(ApiResponse.success(null, "차량이 등록되었습니다."));
}
/** DELETE /account → 회원 탈퇴 */
@DeleteMapping("/account")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteDriverAccount(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId) {
if (userId == null) {
return ResponseEntity.status(401).body(ApiResponse.error("인증이 필요합니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("userId", userId);
driverService.deleteDriverAccount(params);
return ResponseEntity.ok(ApiResponse.success(null, "회원 탈퇴가 완료되었습니다."));
}
}
@@ -0,0 +1,430 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.DynamicFormService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.regex.Pattern;
@RestController
@RequestMapping("/api/dynamic-form")
@RequiredArgsConstructor
@Slf4j
public class DynamicFormController {
private final DynamicFormService dynamicFormService;
private static final Pattern VALID_NAME = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");
// ── POST /save ────────────────────────────────────────────────────────────
@PostMapping("/save")
public ResponseEntity<ApiResponse<Object>> saveFormData(
@RequestAttribute("userId") String userId,
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body,
HttpServletRequest request) {
Object screenIdRaw = body.get("screenId");
String tableName = (String) body.get("tableName");
Map<String, Object> data = (Map<String, Object>) body.get("data");
if (screenIdRaw == null || tableName == null || data == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("필수 필드가 누락되었습니다. (screenId, tableName, data)"));
}
int screenId = ((Number) screenIdRaw).intValue();
Map<String, Object> formDataWithMeta = new LinkedHashMap<>(data);
formDataWithMeta.put("created_by", userId);
formDataWithMeta.put("updated_by", userId);
formDataWithMeta.put("writer", data.getOrDefault("writer", userId));
formDataWithMeta.put("screen_id", screenId);
if (data.containsKey("company_code")) {
formDataWithMeta.put("company_code", data.get("company_code"));
} else if (companyCode != null && !"*".equals(companyCode)) {
formDataWithMeta.put("company_code", companyCode);
}
String ipAddress = Optional.ofNullable(request.getHeader("X-Forwarded-For"))
.orElse(request.getRemoteAddr());
try {
Object result = dynamicFormService.saveFormData(screenId, tableName, formDataWithMeta, ipAddress);
return ResponseEntity.ok(ApiResponse.success(result, "데이터가 성공적으로 저장되었습니다."));
} catch (Exception e) {
log.error("폼 데이터 저장 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "데이터 저장에 실패했습니다."));
}
}
// ── POST /save-enhanced ───────────────────────────────────────────────────
@PostMapping("/save-enhanced")
public ResponseEntity<ApiResponse<Object>> saveFormDataEnhanced(
@RequestAttribute("userId") String userId,
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
Object screenIdRaw = body.get("screenId");
String tableName = (String) body.get("tableName");
Map<String, Object> data = (Map<String, Object>) body.get("data");
if (screenIdRaw == null || tableName == null || data == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("필수 필드가 누락되었습니다. (screenId, tableName, data)"));
}
int screenId = ((Number) screenIdRaw).intValue();
Map<String, Object> formDataWithMeta = new LinkedHashMap<>(data);
formDataWithMeta.put("created_by", userId);
formDataWithMeta.put("updated_by", userId);
formDataWithMeta.put("writer", data.getOrDefault("writer", userId));
formDataWithMeta.put("screen_id", screenId);
if (data.containsKey("company_code")) {
formDataWithMeta.put("company_code", data.get("company_code"));
} else if (companyCode != null && !"*".equals(companyCode)) {
formDataWithMeta.put("company_code", companyCode);
}
try {
Object result = dynamicFormService.saveFormDataEnhanced(screenId, tableName, formDataWithMeta);
return ResponseEntity.ok(ApiResponse.success(result, "데이터가 성공적으로 저장되었습니다."));
} catch (Exception e) {
log.error("개선된 폼 데이터 저장 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "데이터 저장에 실패했습니다."));
}
}
// ── PUT /update-field ─────────────────────────────────────────────────────
@PutMapping("/update-field")
public ResponseEntity<ApiResponse<Object>> updateFieldValue(
@RequestAttribute("userId") String userId,
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
String tableName = (String) body.get("tableName");
String keyField = (String) body.get("keyField");
Object keyValue = body.get("keyValue");
String updateField = (String) body.get("updateField");
Object updateValue = body.get("updateValue");
if (tableName == null || keyField == null || keyValue == null
|| updateField == null || updateValue == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("필수 필드가 누락되었습니다. (tableName, keyField, keyValue, updateField, updateValue)"));
}
if (!VALID_NAME.matcher(tableName).matches()
|| !VALID_NAME.matcher(keyField).matches()
|| !VALID_NAME.matcher(updateField).matches()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("유효하지 않은 테이블명 또는 컬럼명입니다."));
}
try {
Map<String, Object> result = dynamicFormService.updateFieldValue(
tableName, keyField, keyValue, updateField, updateValue, companyCode, userId);
return ResponseEntity.ok(ApiResponse.success(result, "필드 값이 업데이트되었습니다."));
} catch (Exception e) {
log.error("[updateFieldValue] 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "필드 업데이트에 실패했습니다."));
}
}
// ── PUT /{id} ─────────────────────────────────────────────────────────────
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Object>> updateFormData(
@PathVariable String id,
@RequestAttribute("userId") String userId,
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
String tableName = (String) body.get("tableName");
Map<String, Object> data = (Map<String, Object>) body.get("data");
if (tableName == null || data == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("필수 필드가 누락되었습니다. (tableName, data)"));
}
Map<String, Object> formDataWithMeta = new LinkedHashMap<>(data);
formDataWithMeta.put("updated_by", userId);
formDataWithMeta.put("writer", data.getOrDefault("writer", userId));
formDataWithMeta.put("updated_at", new java.util.Date());
try {
Map<String, Object> result = dynamicFormService.updateFormData(id, tableName, formDataWithMeta);
return ResponseEntity.ok(ApiResponse.success(result, "데이터가 성공적으로 업데이트되었습니다."));
} catch (Exception e) {
log.error("폼 데이터 업데이트 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "데이터 업데이트에 실패했습니다."));
}
}
// ── PATCH /{id}/partial ───────────────────────────────────────────────────
@PatchMapping("/{id}/partial")
public ResponseEntity<ApiResponse<Object>> updateFormDataPartial(
@PathVariable String id,
@RequestAttribute("userId") String userId,
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
String tableName = (String) body.get("tableName");
Map<String, Object> originalData = (Map<String, Object>) body.get("originalData");
Map<String, Object> newData = (Map<String, Object>) body.get("newData");
if (tableName == null || originalData == null || newData == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("필수 필드가 누락되었습니다. (tableName, originalData, newData)"));
}
Map<String, Object> newDataWithMeta = new LinkedHashMap<>(newData);
newDataWithMeta.put("updated_by", userId);
newDataWithMeta.put("writer", newData.getOrDefault("writer", userId));
try {
Map<String, Object> result = dynamicFormService.updateFormDataPartial(
id, tableName, originalData, newDataWithMeta);
return ResponseEntity.ok(ApiResponse.success(result, "데이터가 성공적으로 업데이트되었습니다."));
} catch (Exception e) {
log.error("부분 업데이트 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "데이터 업데이트에 실패했습니다."));
}
}
// ── DELETE /{id} ──────────────────────────────────────────────────────────
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteFormData(
@PathVariable String id,
@RequestAttribute("userId") String userId,
@RequestAttribute("companyCode") String companyCode,
@RequestBody(required = false) Map<String, Object> body) {
if (body == null) body = new HashMap<>();
String tableName = (String) body.get("tableName");
Object screenIdRaw = body.get("screenId");
if (tableName == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("필수 필드가 누락되었습니다. (tableName)"));
}
Integer screenId = screenIdRaw != null
? Integer.parseInt(screenIdRaw.toString())
: null;
try {
dynamicFormService.deleteFormData(id, tableName, companyCode, userId, screenId);
return ResponseEntity.ok(ApiResponse.success(null, "데이터가 성공적으로 삭제되었습니다."));
} catch (Exception e) {
log.error("폼 데이터 삭제 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "데이터 삭제에 실패했습니다."));
}
}
// ── GET /screen/{screenId} ────────────────────────────────────────────────
@GetMapping("/screen/{screenId}")
public ResponseEntity<ApiResponse<Object>> getFormDataList(
@PathVariable int screenId,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "") String search,
@RequestParam(defaultValue = "created_at") String sortBy,
@RequestParam(defaultValue = "desc") String sortOrder) {
try {
Map<String, Object> params = new LinkedHashMap<>();
params.put("page", page);
params.put("size", size);
params.put("search", search.isEmpty() ? null : search);
params.put("sortBy", sortBy);
params.put("sortOrder", sortOrder);
Map<String, Object> result = dynamicFormService.getFormDataList(screenId, params);
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("폼 데이터 목록 조회 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "데이터 조회에 실패했습니다."));
}
}
// ── GET /table/{tableName}/columns ────────────────────────────────────────
@GetMapping("/table/{tableName}/columns")
public ResponseEntity<ApiResponse<Object>> getTableColumns(@PathVariable String tableName) {
try {
List<Map<String, Object>> columns = dynamicFormService.getTableColumns(tableName);
Map<String, Object> data = new LinkedHashMap<>();
data.put("tableName", tableName);
data.put("columns", columns);
return ResponseEntity.ok(ApiResponse.success(data));
} catch (Exception e) {
log.error("테이블 컬럼 정보 조회 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "테이블 정보 조회에 실패했습니다."));
}
}
// ── GET /table/{tableName}/primary-keys ───────────────────────────────────
@GetMapping("/table/{tableName}/primary-keys")
public ResponseEntity<ApiResponse<Object>> getTablePrimaryKeys(@PathVariable String tableName) {
if (tableName == null || tableName.isEmpty()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("테이블명이 누락되었습니다."));
}
try {
List<String> primaryKeys = dynamicFormService.getTablePrimaryKeys(tableName);
return ResponseEntity.ok(ApiResponse.success(primaryKeys, "기본키 조회가 완료되었습니다."));
} catch (Exception e) {
log.error("기본키 조회 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "기본키 조회에 실패했습니다."));
}
}
// ── POST /validate ────────────────────────────────────────────────────────
@PostMapping("/validate")
public ResponseEntity<ApiResponse<Object>> validateFormData(
@RequestBody Map<String, Object> body) {
String tableName = (String) body.get("tableName");
Map<String, Object> data = (Map<String, Object>) body.get("data");
if (tableName == null || data == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("필수 필드가 누락되었습니다. (tableName, data)"));
}
try {
Map<String, Object> result = dynamicFormService.validateFormData(tableName, data);
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("폼 데이터 검증 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "데이터 검증에 실패했습니다."));
}
}
// ── GET /location-history/{tripId} ────────────────────────────────────────
@GetMapping("/location-history/{tripId}")
public ResponseEntity<ApiResponse<Object>> getLocationHistory(
@PathVariable String tripId,
@RequestAttribute("companyCode") String companyCode,
@RequestParam(required = false) String userId,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate,
@RequestParam(required = false) Integer limit) {
try {
Map<String, Object> params = new LinkedHashMap<>();
params.put("companyCode", companyCode);
params.put("tripId", tripId);
if (userId != null) params.put("userId", userId);
if (startDate != null) params.put("startDate", startDate);
if (endDate != null) params.put("endDate", endDate);
params.put("limit", limit != null ? limit : 1000);
List<Map<String, Object>> result = dynamicFormService.getLocationHistory(params);
Map<String, Object> response = new LinkedHashMap<>();
response.put("success", true);
response.put("data", result);
response.put("count", result.size());
return ResponseEntity.ok(ApiResponse.success(response));
} catch (Exception e) {
log.error("[getLocationHistory] 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "위치 이력 조회에 실패했습니다."));
}
}
// ── POST /location-history ────────────────────────────────────────────────
@PostMapping("/location-history")
public ResponseEntity<ApiResponse<Object>> saveLocationHistory(
@RequestAttribute("userId") String loginUserId,
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
Double latitude = body.get("latitude") != null ? ((Number) body.get("latitude")).doubleValue() : null;
Double longitude = body.get("longitude") != null ? ((Number) body.get("longitude")).doubleValue() : null;
if (latitude == null || longitude == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("필수 필드가 누락되었습니다. (latitude, longitude)"));
}
String userId = body.containsKey("userId") ? (String) body.get("userId") : loginUserId;
Map<String, Object> params = new LinkedHashMap<>();
params.put("userId", userId);
params.put("companyCode", companyCode);
params.put("latitude", latitude);
params.put("longitude", longitude);
params.put("accuracy", body.get("accuracy"));
params.put("altitude", body.get("altitude"));
params.put("speed", body.get("speed"));
params.put("heading", body.get("heading"));
params.put("tripId", body.get("tripId"));
params.put("tripStatus", body.getOrDefault("tripStatus", "active"));
params.put("departure", body.get("departure"));
params.put("arrival", body.get("arrival"));
params.put("departureName", body.get("departureName"));
params.put("destinationName", body.get("destinationName"));
params.put("recordedAt", body.getOrDefault("recordedAt", new java.util.Date().toInstant().toString()));
params.put("vehicleId", body.get("vehicleId"));
try {
Map<String, Object> result = dynamicFormService.saveLocationHistory(params);
return ResponseEntity.ok(ApiResponse.success(result, "위치 이력이 저장되었습니다."));
} catch (Exception e) {
log.error("[saveLocationHistory] 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "위치 이력 저장에 실패했습니다."));
}
}
// ── GET /{id} ─────────────────────────────────────────────────────────────
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Object>> getFormData(@PathVariable int id) {
try {
Map<String, Object> data = dynamicFormService.getFormData(id);
if (data == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("데이터를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(data));
} catch (Exception e) {
log.error("폼 데이터 단건 조회 실패: {}", e.getMessage(), e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage() : "데이터 조회에 실패했습니다."));
}
}
}
@@ -0,0 +1,241 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.EntityJoinService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Entity 조인 API 컨트롤러.
* Node.js /api/table-management/* 라우트와 동일한 경로/응답 형식을 제공한다.
*/
@RestController
@RequestMapping("/api/table-management")
@RequiredArgsConstructor
@Slf4j
public class EntityJoinController {
private final EntityJoinService entityJoinService;
private final ObjectMapper objectMapper;
// ════════════════════════════════════════════════════════════════════════
// Entity 조인 데이터 조회
// ════════════════════════════════════════════════════════════════════════
/**
* GET /api/table-management/tables/{tableName}/data-with-joins
* Entity 조인이 포함된 테이블 데이터 조회 (페이지네이션)
*/
@GetMapping("/tables/{tableName}/data-with-joins")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTableDataWithJoins(
@PathVariable String tableName,
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> queryParams) {
Map<String, Object> options = new HashMap<>(queryParams);
options.put("companyCode", companyCode);
// search 파라미터 JSON 파싱
Object searchRaw = queryParams.get("search");
if (searchRaw instanceof String s && !s.isBlank()) {
try {
options.put("search", objectMapper.readValue(s, new TypeReference<Map<String, Object>>() {}));
} catch (Exception e) {
log.warn("search 파싱 실패: {}", e.getMessage());
options.remove("search");
}
}
// autoFilter 파싱 (멀티테넌시)
Object autoFilterRaw = queryParams.get("autoFilter");
if (autoFilterRaw instanceof String af && !af.isBlank()) {
try {
Map<String, Object> autoFilter = objectMapper.readValue(af, new TypeReference<>() {});
Boolean enabled = (Boolean) autoFilter.getOrDefault("enabled", false);
if (Boolean.TRUE.equals(enabled)) {
String filterColumn = (String) autoFilter.getOrDefault("filterColumn", "company_code");
Map<String, Object> search = getOrCreateSearch(options);
String finalCode = companyCode;
if ("*".equals(companyCode) && autoFilter.get("companyCodeOverride") != null) {
finalCode = (String) autoFilter.get("companyCodeOverride");
}
search.put(filterColumn, finalCode);
options.put("search", search);
}
} catch (Exception e) {
log.warn("autoFilter 파싱 실패: {}", e.getMessage());
}
}
Map<String, Object> result = entityJoinService.getTableDataWithJoins(tableName, options);
return ResponseEntity.ok(ApiResponse.success(result, "Entity 조인 데이터 조회 성공"));
}
// ════════════════════════════════════════════════════════════════════════
// Entity 조인 설정 관리
// ════════════════════════════════════════════════════════════════════════
/**
* GET /api/table-management/tables/{tableName}/entity-joins
* 테이블의 Entity 조인 설정 조회
*/
@GetMapping("/tables/{tableName}/entity-joins")
public ResponseEntity<ApiResponse<Map<String, Object>>> getEntityJoinConfigs(
@PathVariable String tableName,
@RequestAttribute("companyCode") String companyCode) {
List<Map<String, Object>> joinConfigs =
entityJoinService.detectEntityJoins(tableName, null, companyCode);
Map<String, Object> data = new HashMap<>();
data.put("tableName", tableName);
data.put("joinConfigs", joinConfigs);
data.put("count", joinConfigs.size());
return ResponseEntity.ok(ApiResponse.success(data, "Entity 조인 설정 조회 성공"));
}
/**
* PUT /api/table-management/tables/{tableName}/columns/{columnName}/entity-settings
* 컬럼 Entity 설정 업데이트 (display_column 포함)
*/
@PutMapping("/tables/{tableName}/columns/{columnName}/entity-settings")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateEntitySettings(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
String webType = (String) body.get("webType");
String referenceTable = (String) body.get("referenceTable");
String referenceColumn = (String) body.get("referenceColumn");
if ("entity".equals(webType) && (referenceTable == null || referenceColumn == null)) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("Entity 타입의 경우 referenceTable과 referenceColumn이 필수입니다."));
}
entityJoinService.updateColumnEntitySettings(tableName, columnName, body, companyCode);
// 관련 캐시 무효화
if ("entity".equals(webType) && referenceTable != null) {
String displayColumn = (String) body.get("displayColumn");
String referenceColVal = (String) body.get("referenceColumn");
entityJoinService.invalidateCache(referenceTable, referenceColVal, displayColumn);
}
Map<String, Object> data = new HashMap<>();
data.put("tableName", tableName);
data.put("columnName", columnName);
data.put("settings", Map.of(
"webType", webType != null ? webType : "",
"referenceTable", referenceTable != null ? referenceTable : "",
"referenceColumn", referenceColumn != null ? referenceColumn : "",
"displayColumn", body.getOrDefault("displayColumn", "")));
return ResponseEntity.ok(ApiResponse.success(data, "Entity 설정 업데이트 성공"));
}
// ════════════════════════════════════════════════════════════════════════
// 참조 테이블 정보
// ════════════════════════════════════════════════════════════════════════
/**
* GET /api/table-management/tables/{tableName}/entity-join-columns
* Entity 조인된 테이블의 추가 컬럼 목록 조회 (화면편집기용)
*/
@GetMapping("/tables/{tableName}/entity-join-columns")
public ResponseEntity<ApiResponse<Map<String, Object>>> getEntityJoinColumns(
@PathVariable String tableName,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> result = entityJoinService.getEntityJoinColumns(tableName, companyCode);
return ResponseEntity.ok(ApiResponse.success(result, "Entity 조인 컬럼 조회 성공"));
}
/**
* GET /api/table-management/reference-tables/{tableName}/columns
* 참조 테이블의 표시 가능한 컬럼 목록 조회
*/
@GetMapping("/reference-tables/{tableName}/columns")
public ResponseEntity<ApiResponse<Map<String, Object>>> getReferenceTableColumns(
@PathVariable String tableName,
@RequestAttribute("companyCode") String companyCode) {
List<Map<String, Object>> columns =
entityJoinService.getReferenceTableColumns(tableName, companyCode);
Map<String, Object> data = new HashMap<>();
data.put("tableName", tableName);
data.put("columns", columns);
data.put("count", columns.size());
return ResponseEntity.ok(ApiResponse.success(data, "참조 테이블 컬럼 조회 성공"));
}
// ════════════════════════════════════════════════════════════════════════
// 캐시 관리
// ════════════════════════════════════════════════════════════════════════
/**
* GET /api/table-management/cache/status
* 캐시 상태 조회
*/
@GetMapping("/cache/status")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCacheStatus() {
return ResponseEntity.ok(ApiResponse.success(entityJoinService.getCacheStatus(), "캐시 상태 조회 성공"));
}
/**
* DELETE /api/table-management/cache
* 캐시 무효화 (table/keyColumn/displayColumn 지정 시 부분 무효화)
*/
@DeleteMapping("/cache")
public ResponseEntity<ApiResponse<Map<String, Object>>> invalidateCache(
@RequestParam(required = false) String table,
@RequestParam(required = false) String keyColumn,
@RequestParam(required = false) String displayColumn) {
entityJoinService.invalidateCache(table, keyColumn, displayColumn);
Map<String, Object> data = new HashMap<>();
data.put("target", (table != null)
? table + "." + keyColumn + "." + displayColumn
: "전체");
return ResponseEntity.ok(ApiResponse.success(data, "캐시 무효화 완료"));
}
/**
* POST /api/table-management/cache/preload
* 공통 참조 테이블 자동 캐싱
*/
@PostMapping("/cache/preload")
public ResponseEntity<ApiResponse<Map<String, Object>>> preloadCommonCaches() {
entityJoinService.preloadCommonCaches();
Map<String, Object> cacheStatus = entityJoinService.getCacheStatus();
@SuppressWarnings("unchecked")
List<Map<String, Object>> caches = (List<Map<String, Object>>) cacheStatus.get("caches");
Map<String, Object> data = new HashMap<>();
data.put("preloadedCaches", caches != null ? caches.size() : 0);
data.put("caches", caches);
return ResponseEntity.ok(ApiResponse.success(data, "공통 참조 테이블 캐싱 완료"));
}
// ─── 내부 유틸 ──────────────────────────────────────────────────────────
@SuppressWarnings("unchecked")
private Map<String, Object> getOrCreateSearch(Map<String, Object> options) {
Object existing = options.get("search");
if (existing instanceof Map) return (Map<String, Object>) existing;
Map<String, Object> search = new HashMap<>();
options.put("search", search);
return search;
}
}
@@ -0,0 +1,79 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.EntityReferenceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/entity-reference")
@RequiredArgsConstructor
@Slf4j
public class EntityReferenceController {
private final EntityReferenceService entityReferenceService;
/**
* GET /api/entity-reference/code/:codeCategory
* 공통 코드 데이터 조회
*
* NOTE: Spring MVC는 리터럴 경로 세그먼트("code")를 변수 경로({tableName})보다 우선하므로
* /code/{codeCategory} 가 /{tableName}/{columnName} 보다 먼저 매핑됨.
*/
@GetMapping("/code/{codeCategory}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCodeData(
@PathVariable String codeCategory,
@RequestParam(required = false, defaultValue = "100") Integer limit,
@RequestParam(required = false) String search,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("codeCategory", codeCategory);
params.put("companyCode", companyCode);
params.put("limit", limit);
if (search != null) params.put("search", search);
try {
return ResponseEntity.ok(ApiResponse.success(entityReferenceService.getCodeData(params)));
} catch (Exception e) {
log.error("공통 코드 데이터 조회 실패: codeCategory={}", codeCategory, e);
return ResponseEntity.status(500).body(ApiResponse.error("공통 코드 데이터 조회 중 오류가 발생했습니다."));
}
}
/**
* GET /api/entity-reference/:tableName/:columnName
* 엔티티 참조 데이터 조회
*/
@GetMapping("/{tableName}/{columnName}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getEntityReferenceData(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestParam(required = false, defaultValue = "100") Integer limit,
@RequestParam(required = false) String search,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("columnName", columnName);
params.put("companyCode", companyCode);
params.put("limit", limit);
if (search != null) params.put("search", search);
try {
return ResponseEntity.ok(ApiResponse.success(entityReferenceService.getEntityReferenceData(params)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (IllegalStateException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("엔티티 참조 데이터 조회 실패: {}.{}", tableName, columnName, e);
return ResponseEntity.status(500).body(ApiResponse.error("엔티티 참조 데이터 조회 중 오류가 발생했습니다."));
}
}
}
@@ -0,0 +1,148 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.EntitySearchService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Entity Search / Options / Distinct 통합 컨트롤러
*
* Node.js 라우트 대응:
* GET /api/entity-search/:tableName → searchEntity
* GET /api/entity/:tableName/options → getEntityOptions
* GET /api/entity/:tableName/distinct/:col → getDistinctColumnValues
*/
@RestController
@RequiredArgsConstructor
@Slf4j
public class EntitySearchController {
private final EntitySearchService entitySearchService;
// ================================================================
// GET /api/entity-search/{tableName}
// ================================================================
@GetMapping("/api/entity-search/{tableName}")
public ResponseEntity<?> searchEntity(
@PathVariable String tableName,
@RequestParam(defaultValue = "") String searchText,
@RequestParam(defaultValue = "") String searchFields,
@RequestParam(defaultValue = "{}") String filterCondition,
@RequestParam(defaultValue = "1") String page,
@RequestParam(defaultValue = "20") String limit,
@RequestAttribute("companyCode") String companyCode) {
if (tableName == null || tableName.isBlank()
|| "undefined".equals(tableName) || "null".equals(tableName)) {
return ResponseEntity.status(400).body(
ApiResponse.error("테이블명이 지정되지 않았습니다. 컴포넌트 설정에서 sourceTable을 확인해주세요."));
}
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("companyCode", companyCode);
params.put("searchText", searchText);
params.put("searchFields", searchFields);
params.put("filterCondition", filterCondition);
params.put("page", page);
params.put("limit", limit);
try {
Map<String, Object> result = entitySearchService.searchEntity(params);
// Node.js 응답 형식: { success, data, pagination } — pagination이 data와 같은 레벨
Map<String, Object> response = new LinkedHashMap<>();
response.put("success", true);
response.put("data", result.get("data"));
response.put("pagination", result.get("pagination"));
return ResponseEntity.ok(response);
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("엔티티 검색 오류 table={}", tableName, e);
return ResponseEntity.status(500).body(ApiResponse.error(e.getMessage()));
}
}
// ================================================================
// GET /api/entity/{tableName}/options
// ================================================================
@GetMapping("/api/entity/{tableName}/options")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getEntityOptions(
@PathVariable String tableName,
@RequestParam(defaultValue = "id") String value,
@RequestParam(defaultValue = "name") String label,
@RequestParam(required = false) String fields,
@RequestParam(required = false) String filters,
@RequestAttribute("companyCode") String companyCode) {
if (tableName == null || tableName.isBlank()
|| "undefined".equals(tableName) || "null".equals(tableName)) {
return ResponseEntity.status(400).body(ApiResponse.error("테이블명이 지정되지 않았습니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("companyCode", companyCode);
params.put("value", value);
params.put("label", label);
params.put("fields", fields);
params.put("filters", filters);
try {
return ResponseEntity.ok(ApiResponse.success(entitySearchService.getEntityOptions(params)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("엔티티 옵션 조회 오류 table={}", tableName, e);
return ResponseEntity.status(500).body(ApiResponse.error(e.getMessage()));
}
}
// ================================================================
// GET /api/entity/{tableName}/distinct/{columnName}
// ================================================================
@GetMapping("/api/entity/{tableName}/distinct/{columnName}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDistinctColumnValues(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestParam(required = false) String labelColumn,
@RequestParam(required = false) String filters,
@RequestAttribute("companyCode") String companyCode) {
if (tableName == null || tableName.isBlank()
|| "undefined".equals(tableName) || "null".equals(tableName)) {
return ResponseEntity.status(400).body(ApiResponse.error("테이블명이 지정되지 않았습니다."));
}
if (columnName == null || columnName.isBlank()
|| "undefined".equals(columnName) || "null".equals(columnName)) {
return ResponseEntity.status(400).body(ApiResponse.error("컬럼명이 지정되지 않았습니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("columnName", columnName);
params.put("companyCode", companyCode);
params.put("labelColumn", labelColumn);
params.put("filters", filters);
try {
return ResponseEntity.ok(ApiResponse.success(entitySearchService.getDistinctColumnValues(params)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("컬럼 DISTINCT 조회 오류 table={} column={}", tableName, columnName, e);
return ResponseEntity.status(500).body(ApiResponse.error(e.getMessage()));
}
}
}
@@ -0,0 +1,67 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ExcelMappingService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/excel-mapping")
@RequiredArgsConstructor
@Slf4j
public class ExcelMappingController {
private final ExcelMappingService excelMappingService;
@PostMapping("/find")
public ResponseEntity<ApiResponse<Map<String, Object>>> findMappingByColumns(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
if (body.get("tableName") == null || body.get("excelColumns") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("tableName과 excelColumns는 필수입니다."));
}
body.put("companyCode", companyCode);
Map<String, Object> result = excelMappingService.findMappingByColumns(body);
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping("/save")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveMappingTemplate(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
if (body.get("tableName") == null || body.get("excelColumns") == null || body.get("columnMappings") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("tableName, excelColumns, columnMappings는 필수입니다."));
}
body.put("companyCode", companyCode);
Map<String, Object> result = excelMappingService.saveMappingTemplate(body);
return ResponseEntity.ok(ApiResponse.success(result, "매핑 템플릿이 저장되었습니다."));
}
@GetMapping("/list/{tableName}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMappingTemplates(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName) {
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("companyCode", companyCode);
List<Map<String, Object>> result = excelMappingService.getMappingTemplates(params);
return ResponseEntity.ok(ApiResponse.success(result));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteMappingTemplate(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
boolean deleted = excelMappingService.deleteMappingTemplate(params);
if (!deleted) {
return ResponseEntity.status(404).body(ApiResponse.error("매핑 템플릿을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "매핑 템플릿이 삭제되었습니다."));
}
}
@@ -0,0 +1,167 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ExternalCallConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/external-call-configs")
@RequiredArgsConstructor
@Slf4j
public class ExternalCallConfigController {
private final ExternalCallConfigService externalCallConfigService;
// ── 목록 조회 ──────────────────────────────────────────────────────────
/**
* GET /api/external-call-configs
* ?call_type=&api_type=&is_active=Y&search=&page=1&limit=20
*/
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getExternalCallConfigList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> queryParams) {
queryParams.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
externalCallConfigService.getExternalCallConfigList(queryParams)));
}
// ── 버튼 제어용 목록 (리터럴 경로 - /{id} 보다 먼저 선언) ────────────────
/**
* GET /api/external-call-configs/for-button-control
*/
@GetMapping("/for-button-control")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getExternalCallConfigForButtonControl(
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
externalCallConfigService.getExternalCallConfigForButtonControl(companyCode)));
}
// ── 단건 조회 ──────────────────────────────────────────────────────────
/**
* GET /api/external-call-configs/:id
*/
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getExternalCallConfigInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
Map<String, Object> config = externalCallConfigService.getExternalCallConfigInfo(params);
if (config == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("외부 호출 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(config, "외부 호출 설정 조회 완료"));
}
// ── 생성 ───────────────────────────────────────────────────────────────
/**
* POST /api/external-call-configs
*/
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertExternalCallConfig(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("created_by", userId);
body.put("updated_by", userId);
try {
Map<String, Object> created = externalCallConfigService.insertExternalCallConfig(body);
return ResponseEntity.status(201)
.body(ApiResponse.success(created, "외부 호출 설정이 성공적으로 생성되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
}
}
// ── 수정 ───────────────────────────────────────────────────────────────
/**
* PUT /api/external-call-configs/:id
*/
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateExternalCallConfig(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable int id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("updated_by", userId);
body.put("id", id);
try {
Map<String, Object> updated = externalCallConfigService.updateExternalCallConfig(body);
return ResponseEntity.ok(ApiResponse.success(updated, "외부 호출 설정이 성공적으로 수정되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
// ── 삭제 (논리 삭제) ────────────────────────────────────────────────────
/**
* DELETE /api/external-call-configs/:id
*/
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteExternalCallConfig(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable int id) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
params.put("updatedBy", userId);
try {
externalCallConfigService.deleteExternalCallConfig(params);
return ResponseEntity.ok(ApiResponse.success(null, "외부 호출 설정이 성공적으로 삭제되었습니다."));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
// ── 설정 테스트 ────────────────────────────────────────────────────────
/**
* POST /api/external-call-configs/:id/test
*/
@PostMapping("/{id}/test")
public ResponseEntity<ApiResponse<Map<String, Object>>> testExternalCallConfig(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id) {
Map<String, Object> result = externalCallConfigService.testExternalCallConfig(id);
String message = (String) result.get("message");
return ResponseEntity.ok(ApiResponse.success(result, message));
}
// ── 설정 실행 ──────────────────────────────────────────────────────────
/**
* POST /api/external-call-configs/:id/execute
*/
@PostMapping("/{id}/execute")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeExternalCallConfig(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable int id,
@RequestBody(required = false) Map<String, Object> body) {
Map<String, Object> contextData = body != null ? new HashMap<>(body) : new HashMap<>();
contextData.put("userId", userId);
contextData.put("companyCode", companyCode);
Map<String, Object> result = externalCallConfigService.executeExternalCallConfig(id, contextData);
String message = (String) result.get("message");
return ResponseEntity.ok(ApiResponse.success(result, message));
}
}
@@ -0,0 +1,127 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ExternalCallService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/external-calls")
@RequiredArgsConstructor
@Slf4j
public class ExternalCallController {
private final ExternalCallService service;
// ── 외부 호출 테스트 ────────────────────────────────────────────────────────
@SuppressWarnings("unchecked")
@PostMapping("/test")
public ResponseEntity<ApiResponse<Map<String, Object>>> testExternalCall(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
Map<String, Object> settings = (Map<String, Object>) body.get("settings");
if (settings == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("외부 호출 설정이 필요합니다."));
}
// 설정 검증
Map<String, Object> validation = service.validateExternalCallSettings(settings);
if (!Boolean.TRUE.equals(validation.get("valid"))) {
Map<String, Object> errorData = new LinkedHashMap<>();
errorData.put("errors", validation.get("errors"));
return ResponseEntity.badRequest()
.body(ApiResponse.error("설정 검증 실패"));
}
// 테스트 요청 생성
Map<String, Object> templateData = (Map<String, Object>) body.get("templateData");
if (templateData == null) {
templateData = Map.of(
"recordCount", 5,
"tableName", "test_table",
"timestamp", new Date().toString()
);
}
Map<String, Object> request = new LinkedHashMap<>();
request.put("diagramId", 0);
request.put("relationshipId", "test");
request.put("settings", settings);
request.put("templateData", templateData);
Map<String, Object> result = service.executeExternalCall(request);
boolean success = Boolean.TRUE.equals(result.get("success"));
if (success) {
return ResponseEntity.ok(ApiResponse.success(result, "외부 호출 테스트 성공"));
}
return ResponseEntity.ok(ApiResponse.success(result, "외부 호출 테스트 실패"));
}
// ── 외부 호출 실행 ──────────────────────────────────────────────────────────
@SuppressWarnings("unchecked")
@PostMapping("/execute")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeExternalCall(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
Object diagramId = body.get("diagramId");
String relationshipId = (String) body.get("relationshipId");
Map<String, Object> settings = (Map<String, Object>) body.get("settings");
if (diagramId == null || relationshipId == null || settings == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("필수 파라미터가 누락되었습니다. (diagramId, relationshipId, settings)"));
}
// 설정 검증
Map<String, Object> validation = service.validateExternalCallSettings(settings);
if (!Boolean.TRUE.equals(validation.get("valid"))) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("설정 검증 실패"));
}
Map<String, Object> result = service.executeExternalCall(body);
boolean success = Boolean.TRUE.equals(result.get("success"));
if (success) {
return ResponseEntity.ok(ApiResponse.success(result, "외부 호출 실행 성공"));
}
return ResponseEntity.ok(ApiResponse.success(result, "외부 호출 실행 실패"));
}
// ── 지원 타입 목록 ──────────────────────────────────────────────────────────
@GetMapping("/types")
public ResponseEntity<ApiResponse<Map<String, Object>>> getExternalCallTypeList(
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> types = service.getExternalCallTypeList();
return ResponseEntity.ok(ApiResponse.success(types, "지원되는 외부 호출 타입 목록"));
}
// ── 설정 검증 ──────────────────────────────────────────────────────────────
@SuppressWarnings("unchecked")
@PostMapping("/validate")
public ResponseEntity<ApiResponse<Map<String, Object>>> validateExternalCallSettings(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
Map<String, Object> settings = (Map<String, Object>) body.get("settings");
if (settings == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("검증할 설정이 필요합니다."));
}
Map<String, Object> validation = service.validateExternalCallSettings(settings);
return ResponseEntity.ok(ApiResponse.success(validation, "설정 검증 완료"));
}
}
@@ -0,0 +1,281 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ExternalDbConnectionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* 외부 DB 연결 관리 API
* Base: /api/external-db-connections
*
* 경로 우선순위 (Spring MVC: 리터럴 > 변수):
* GET /types/supported, /pool-status, /grouped, /control/active → literal (먼저 매핑)
* GET /{id} → variable
* GET /{id}/tables/{tableName}/columns → 3-segment path
*/
@RestController
@RequestMapping("/api/external-db-connections")
@RequiredArgsConstructor
@Slf4j
public class ExternalDbConnectionController {
private final ExternalDbConnectionService service;
// ── 지원 DB 타입 목록 ──────────────────────────────────────────────────────
/**
* GET /api/external-db-connections/types/supported
*/
@GetMapping("/types/supported")
public ResponseEntity<ApiResponse<Map<String, Object>>> getSupportedTypes(
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(
ApiResponse.success(service.getSupportedTypes(), "지원하는 DB 타입 목록을 조회했습니다."));
}
// ── 연결 풀 상태 ────────────────────────────────────────────────────────────
/**
* GET /api/external-db-connections/pool-status
*/
@GetMapping("/pool-status")
public ResponseEntity<ApiResponse<Map<String, Object>>> getPoolStatus(
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> status = service.getPoolStatus();
String msg = status.get("totalPools") + "개의 연결 풀 상태를 조회했습니다.";
return ResponseEntity.ok(ApiResponse.success(status, msg));
}
// ── 그룹화된 목록 조회 ─────────────────────────────────────────────────────
/**
* GET /api/external-db-connections/grouped
*/
@GetMapping("/grouped")
public ResponseEntity<ApiResponse<Map<String, Object>>> getConnectionsGroupedByType(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> queryParams) {
buildFilter(queryParams, companyCode, (String) queryParams.get("company_code"));
Map<String, Object> grouped = service.getConnectionsGroupedByType(queryParams);
return ResponseEntity.ok(
ApiResponse.success(grouped, "DB 타입별로 그룹화된 연결 목록을 조회했습니다."));
}
// ── 제어관리용 활성 연결 목록 ─────────────────────────────────────────────
/**
* GET /api/external-db-connections/control/active
*/
@GetMapping("/control/active")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getActiveConnectionsForControl(
@RequestAttribute("companyCode") String companyCode,
@RequestParam(required = false) String company_code) {
Map<String, Object> params = new HashMap<>();
buildFilter(params, companyCode, company_code);
List<Map<String, Object>> list = service.getActiveConnectionsForControl(params);
return ResponseEntity.ok(
ApiResponse.success(list, "제어관리용 활성 커넥션 목록을 조회했습니다."));
}
// ── 목록 조회 ──────────────────────────────────────────────────────────────
/**
* GET /api/external-db-connections
*/
@GetMapping
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getConnections(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> queryParams) {
buildFilter(queryParams, companyCode, (String) queryParams.get("company_code"));
List<Map<String, Object>> list = service.getConnections(queryParams);
return ResponseEntity.ok(
ApiResponse.success(list, list.size() + "개의 연결 설정을 조회했습니다."));
}
// ── 단건 조회 ──────────────────────────────────────────────────────────────
/**
* GET /api/external-db-connections/{id}
*/
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getConnectionById(
@RequestAttribute("companyCode") String companyCode,
@PathVariable long id) {
Map<String, Object> conn = service.getConnectionById(id);
if (conn == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("해당 연결 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(conn, "연결 설정을 조회했습니다."));
}
// ── 생성 ───────────────────────────────────────────────────────────────────
/**
* POST /api/external-db-connections
*/
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createConnection(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
body.put("created_by", userId);
body.put("updated_by", userId);
try {
Map<String, Object> created = service.createConnection(body);
return ResponseEntity.status(201)
.body(ApiResponse.success(created, "연결 설정이 생성되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
}
}
// ── 수정 ───────────────────────────────────────────────────────────────────
/**
* PUT /api/external-db-connections/{id}
*/
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateConnection(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable long id,
@RequestBody Map<String, Object> body) {
body.put("updated_by", userId);
try {
Map<String, Object> updated = service.updateConnection(id, body);
return ResponseEntity.ok(ApiResponse.success(updated, "연결 설정이 수정되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
// ── 삭제 ───────────────────────────────────────────────────────────────────
/**
* DELETE /api/external-db-connections/{id}
*/
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable long id) {
try {
service.deleteConnection(id, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "연결 설정이 삭제되었습니다."));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
// ── 연결 테스트 ────────────────────────────────────────────────────────────
/**
* POST /api/external-db-connections/{id}/test
*/
@PostMapping("/{id}/test")
public ResponseEntity<ApiResponse<Map<String, Object>>> testConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable long id,
@RequestBody(required = false) Map<String, Object> body) {
String testPassword = body != null ? (String) body.get("password") : null;
Map<String, Object> result = service.testConnectionById(id, testPassword);
return ResponseEntity.ok(ApiResponse.success(result,
Boolean.TRUE.equals(result.get("success")) ? "연결 성공" : "연결 실패"));
}
// ── 쿼리 실행 ──────────────────────────────────────────────────────────────
/**
* POST /api/external-db-connections/{id}/execute
*/
@PostMapping("/{id}/execute")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> executeQuery(
@RequestAttribute("companyCode") String companyCode,
@PathVariable long id,
@RequestBody Map<String, Object> body) {
String sql = (String) body.get("query");
if (sql == null || sql.isBlank()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("쿼리가 입력되지 않았습니다."));
}
try {
Map<String, Object> result = service.executeQuery(id, sql);
@SuppressWarnings("unchecked")
List<Map<String, Object>> rows = (List<Map<String, Object>>) result.get("data");
return ResponseEntity.ok(
ApiResponse.success(rows, "쿼리가 성공적으로 실행되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
} catch (UnsupportedOperationException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
}
}
// ── 테이블 목록 ────────────────────────────────────────────────────────────
/**
* GET /api/external-db-connections/{id}/tables
*/
@GetMapping("/{id}/tables")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTables(
@RequestAttribute("companyCode") String companyCode,
@PathVariable long id) {
try {
List<Map<String, Object>> tables = service.getTables(id);
return ResponseEntity.ok(
ApiResponse.success(tables, "테이블 목록을 조회했습니다."));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
// ── 테이블 컬럼 목록 ────────────────────────────────────────────────────────
/**
* GET /api/external-db-connections/{id}/tables/{tableName}/columns
*/
@GetMapping("/{id}/tables/{tableName}/columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTableColumns(
@RequestAttribute("companyCode") String companyCode,
@PathVariable long id,
@PathVariable String tableName) {
if (tableName == null || tableName.isBlank()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("테이블명이 입력되지 않았습니다."));
}
try {
List<Map<String, Object>> columns = service.getTableColumns(id, tableName);
return ResponseEntity.ok(
ApiResponse.success(columns, "컬럼 정보를 조회했습니다."));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
}
}
// ── 내부 유틸 ──────────────────────────────────────────────────────────────
/**
* company_code 필터 적용.
* SUPER_ADMIN("*"): 쿼리 파라미터 company_code 우선, 없으면 전체 조회
* 일반 사용자: 자사 코드 강제 적용
*/
private void buildFilter(Map<String, Object> params, String userCompanyCode, String queryCompanyCode) {
if ("*".equals(userCompanyCode)) {
// SUPER_ADMIN: query param이 있으면 그것, 없으면 전체(null → WHERE 없음)
if (queryCompanyCode != null && !queryCompanyCode.isBlank()) {
params.put("filterCompanyCode", queryCompanyCode);
}
// filterCompanyCode 없으면 전체 조회
} else {
// 일반 사용자: 강제 적용
params.put("filterCompanyCode", userCompanyCode);
}
}
}
@@ -0,0 +1,146 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ExternalRestApiConnectionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/external-rest-api-connections")
@RequiredArgsConstructor
@Slf4j
public class ExternalRestApiConnectionController {
private final ExternalRestApiConnectionService service;
// ── 목록 조회 ──────────────────────────────────────────────────────────────
@GetMapping
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getConnections(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> queryParams) {
queryParams.put("companyCode", companyCode);
List<Map<String, Object>> list = service.getExternalRestApiConnectionList(queryParams);
return ResponseEntity.ok(
ApiResponse.success(list, list.size() + "개의 REST API 연결을 조회했습니다."));
}
// ── 단건 조회 ──────────────────────────────────────────────────────────────
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getConnectionById(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
Map<String, Object> conn = service.getExternalRestApiConnectionInfo(params);
if (conn == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("해당 REST API 연결을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(conn, "REST API 연결을 조회했습니다."));
}
// ── 생성 ───────────────────────────────────────────────────────────────────
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createConnection(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("createdBy", userId);
try {
Map<String, Object> created = service.insertExternalRestApiConnection(body);
return ResponseEntity.status(201)
.body(ApiResponse.success(created, "REST API 연결이 생성되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
}
}
// ── 수정 ───────────────────────────────────────────────────────────────────
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateConnection(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable int id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("updatedBy", userId);
Map<String, Object> updated = service.updateExternalRestApiConnection(id, body);
if (updated == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("해당 REST API 연결을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(updated, "REST API 연결이 수정되었습니다."));
}
// ── 삭제 ───────────────────────────────────────────────────────────────────
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
boolean deleted = service.deleteExternalRestApiConnection(id, params);
if (!deleted) {
return ResponseEntity.status(404)
.body(ApiResponse.error("해당 REST API 연결을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "REST API 연결이 삭제되었습니다."));
}
// ── 연결 테스트 (요청 데이터 기반) ──────────────────────────────────────────
@PostMapping("/test")
public ResponseEntity<ApiResponse<Map<String, Object>>> testConnection(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
Map<String, Object> result = service.testConnection(body, companyCode);
boolean success = Boolean.TRUE.equals(result.get("success"));
return ResponseEntity.ok(ApiResponse.success(result,
success ? "연결 테스트 성공" : "연결 테스트 실패"));
}
// ── 연결 테스트 (ID 기반) ───────────────────────────────────────────────────
@PostMapping("/{id}/test")
public ResponseEntity<ApiResponse<Map<String, Object>>> testConnectionById(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id,
@RequestBody(required = false) Map<String, Object> body) {
String endpoint = body != null ? (String) body.get("endpoint") : null;
Map<String, Object> result = service.testConnectionById(id, endpoint);
boolean success = Boolean.TRUE.equals(result.get("success"));
return ResponseEntity.ok(ApiResponse.success(result,
success ? "연결 테스트 성공" : "연결 테스트 실패"));
}
// ── 데이터 조회 (프록시) ────────────────────────────────────────────────────
@PostMapping("/{id}/fetch")
public ResponseEntity<ApiResponse<Map<String, Object>>> fetchData(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id,
@RequestBody(required = false) Map<String, Object> body) {
Map<String, Object> params = body != null ? new HashMap<>(body) : new HashMap<>();
params.put("companyCode", companyCode);
String endpoint = (String) params.get("endpoint");
String jsonPath = (String) params.get("json_path");
Map<String, Object> result = service.fetchData(id, endpoint, jsonPath, params);
boolean success = Boolean.TRUE.equals(result.get("success"));
if (!success) {
return ResponseEntity.badRequest().body(ApiResponse.error(
(String) result.getOrDefault("message", "데이터 조회 실패")));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
}
@@ -0,0 +1,177 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.FileService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
@RestController
@RequestMapping("/api/files")
@RequiredArgsConstructor
@Slf4j
public class FileController {
private final FileService fileService;
// ── 목록 조회 ─────────────────────────────────────────────────────────────
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getFileList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(fileService.getFileList(params)));
}
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getFileListAlias(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(fileService.getFileList(params)));
}
// ── 파일 정보 (public) ────────────────────────────────────────────────────
@GetMapping("/info/{objid}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getFileInfo(
@PathVariable Long objid) {
Map<String, Object> params = new HashMap<>();
params.put("objid", objid);
Map<String, Object> fileInfo = fileService.getFileInfoPublic(params);
if (fileInfo == null) {
return ResponseEntity.status(404).body(ApiResponse.error("파일을 찾을 수 없습니다."));
}
// 공개 응답: 메타데이터만 반환
Map<String, Object> result = new HashMap<>();
result.put("objid", fileInfo.get("objid"));
result.put("realFileName", fileInfo.get("realFileName"));
result.put("fileSize", fileInfo.get("fileSize"));
result.put("fileExt", fileInfo.get("fileExt"));
result.put("filePath", fileInfo.get("filePath"));
result.put("regdate", fileInfo.get("regdate"));
result.put("isRepresentative", fileInfo.get("isRepresentative"));
return ResponseEntity.ok(ApiResponse.success(result));
}
// ── 타겟별 파일 목록 ─────────────────────────────────────────────────────
@GetMapping("/linked/{tableName}/{recordId}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getLinkedFiles(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@PathVariable String recordId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("targetObjid", tableName + ":" + recordId);
return ResponseEntity.ok(ApiResponse.success(fileService.getFilesByTarget(params)));
}
// ── 업로드 ────────────────────────────────────────────────────────────────
@PostMapping("/upload")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> uploadFiles(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestParam("file") List<MultipartFile> files,
@RequestParam Map<String, Object> params) {
try {
params.put("companyCode", companyCode);
params.put("userId", userId != null ? userId : "system");
List<Map<String, Object>> results = fileService.uploadFiles(files, params);
return ResponseEntity.ok(ApiResponse.success(results, "파일 업로드 완료"));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
} catch (IOException e) {
log.error("파일 업로드 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("파일 업로드 중 오류가 발생했습니다."));
}
}
// ── 다운로드 ─────────────────────────────────────────────────────────────
@GetMapping("/download/{objid}")
public ResponseEntity<Resource> downloadFile(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long objid) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("objid", objid);
Map<String, Object> fileInfo = fileService.getFileInfo(params);
if (fileInfo == null) {
return ResponseEntity.notFound().build();
}
try {
Path filePath = fileService.resolveFilePath(fileInfo);
if (!Files.exists(filePath)) {
return ResponseEntity.notFound().build();
}
String realFileName = (String) fileInfo.getOrDefault("realFileName", "download");
String encodedName = URLEncoder.encode(realFileName, StandardCharsets.UTF_8)
.replace("+", "%20");
Resource resource = new FileSystemResource(filePath);
String contentType = Files.probeContentType(filePath);
if (contentType == null) contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedName)
.contentType(MediaType.parseMediaType(contentType))
.body(resource);
} catch (IOException e) {
log.error("파일 다운로드 실패: objid={}", objid, e);
return ResponseEntity.status(500).build();
}
}
// ── 미리보기 (public) ─────────────────────────────────────────────────────
@GetMapping("/preview/{objid}")
public ResponseEntity<Resource> previewFile(@PathVariable Long objid) {
Map<String, Object> params = new HashMap<>();
params.put("objid", objid);
Map<String, Object> fileInfo = fileService.getFileInfoPublic(params);
if (fileInfo == null) return ResponseEntity.notFound().build();
try {
Path filePath = fileService.resolveFilePath(fileInfo);
if (!Files.exists(filePath)) return ResponseEntity.notFound().build();
String contentType = Files.probeContentType(filePath);
if (contentType == null) contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
Resource resource = new FileSystemResource(filePath);
return ResponseEntity.ok()
.header(HttpHeaders.CACHE_CONTROL, "max-age=3600")
.contentType(MediaType.parseMediaType(contentType))
.body(resource);
} catch (IOException e) {
log.error("파일 미리보기 실패: objid={}", objid, e);
return ResponseEntity.status(500).build();
}
}
// ── 삭제 (소프트 삭제) ────────────────────────────────────────────────────
@DeleteMapping("/{objid}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteFile(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long objid) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("objid", objid);
fileService.deleteFile(params);
return ResponseEntity.ok(ApiResponse.success(null, "파일이 삭제되었습니다."));
}
}
@@ -0,0 +1,558 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.FlowService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* Flow API Controller
* Base path: /api/flows
*
* Definitions : POST/GET/GET-detail/PUT/DELETE /definitions[/:id]
* Steps : GET/POST /definitions/:flowId/steps
* PUT/DELETE /steps/:stepId
* Connections : GET /connections/:flowId
* POST /connections
* DELETE /connections/:connectionId
* Execution : GET /:flowId/step/:stepId/count|list|column-labels
* GET /:flowId/steps/counts
* PUT /:flowId/step/:stepId/data/:recordId
* Data move : POST /move, /move-batch
* Audit : GET /audit/:flowId, /audit/:flowId/:recordId
* Procedures : GET /procedures, /procedures/:name/parameters
*/
@RestController
@RequestMapping("/api/flows")
@RequiredArgsConstructor
@Slf4j
public class FlowController {
private final FlowService service;
// ══════════════════════════════════════════════════════════
// Definitions
// ══════════════════════════════════════════════════════════
@GetMapping("/definitions")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowDefinitionList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getFlowDefinitionList(params)));
}
@GetMapping("/definitions/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getFlowDefinitionInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("id") int id) {
Map<String, Object> detail = service.getFlowDefinitionInfo(id, companyCode);
if (detail == null) {
return ResponseEntity.status(404).body(ApiResponse.error("Flow definition not found"));
}
return ResponseEntity.ok(ApiResponse.success(detail));
}
@PostMapping("/definitions")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertFlowDefinition(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
if (body.get("name") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("Name is required"));
}
// 테이블 존재 확인 (REST API / multi 제외)
String tableName = (String) body.get("tableName");
String dbSource = (String) body.getOrDefault("dbSourceType", "internal");
boolean skipCheck = "restapi".equals(dbSource)
|| "multi_restapi".equals(dbSource)
|| "multi_external_db".equals(dbSource)
|| (tableName != null && (tableName.startsWith("_restapi_")
|| tableName.startsWith("_multi_")));
if (tableName != null && !skipCheck && !service.checkTableExists(tableName)) {
return ResponseEntity.status(400)
.body(ApiResponse.error("Table '" + tableName + "' does not exist"));
}
try {
Map<String, Object> created = service.insertFlowDefinition(body, userId, companyCode);
return ResponseEntity.ok(ApiResponse.success(created));
} catch (Exception e) {
log.error("플로우 정의 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to create flow definition"));
}
}
@PutMapping("/definitions/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateFlowDefinition(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("id") int id,
@RequestBody Map<String, Object> body) {
if (service.getFlowDefinitionById(id, companyCode) == null) {
return ResponseEntity.status(404).body(ApiResponse.error("Flow definition not found"));
}
try {
Map<String, Object> updated = service.updateFlowDefinition(id, body, companyCode);
return ResponseEntity.ok(ApiResponse.success(updated));
} catch (Exception e) {
log.error("플로우 정의 수정 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to update flow definition"));
}
}
@DeleteMapping("/definitions/{id}")
public ResponseEntity<ApiResponse<Void>> deleteFlowDefinition(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("id") int id) {
boolean deleted = service.deleteFlowDefinition(id, companyCode);
if (!deleted) {
return ResponseEntity.status(404).body(ApiResponse.error("Flow definition not found"));
}
return ResponseEntity.ok(ApiResponse.success(null, "Flow definition deleted successfully"));
}
// ══════════════════════════════════════════════════════════
// Steps (under /definitions/:flowId/steps or /steps/:stepId)
// ══════════════════════════════════════════════════════════
@GetMapping("/definitions/{flowId}/steps")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowStepList(
@PathVariable("flowId") int flowId) {
return ResponseEntity.ok(ApiResponse.success(service.getFlowStepList(flowId)));
}
@PostMapping("/definitions/{flowId}/steps")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertFlowStep(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("flowId") int flowId,
@RequestBody Map<String, Object> body) {
if (body.get("stepName") == null || body.get("stepOrder") == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("stepName and stepOrder are required"));
}
// 플로우 소유권 확인
if (service.getFlowDefinitionById(flowId, companyCode) == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("Flow definition not found or access denied"));
}
try {
Map<String, Object> step = service.insertFlowStep(flowId, body);
return ResponseEntity.ok(ApiResponse.success(step));
} catch (Exception e) {
log.error("플로우 스텝 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to create flow step"));
}
}
@PutMapping("/steps/{stepId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateFlowStep(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("stepId") int stepId,
@RequestBody Map<String, Object> body) {
// 스텝 소유권 검증: 스텝이 속한 플로우가 요청자 회사 소유인지 확인
Map<String, Object> existingStep = service.getFlowStepById(stepId);
if (existingStep != null) {
int flowDefId = toInt(existingStep.get("flow_definition_id"), 0);
if (service.getFlowDefinitionById(flowDefId, companyCode) == null) {
return ResponseEntity.status(403)
.body(ApiResponse.error("Access denied: flow does not belong to your company"));
}
}
try {
Map<String, Object> step = service.updateFlowStep(stepId, body);
if (step == null) {
return ResponseEntity.status(404).body(ApiResponse.error("Flow step not found"));
}
return ResponseEntity.ok(ApiResponse.success(step));
} catch (Exception e) {
log.error("플로우 스텝 수정 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to update flow step"));
}
}
@DeleteMapping("/steps/{stepId}")
public ResponseEntity<ApiResponse<Void>> deleteFlowStep(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("stepId") int stepId) {
Map<String, Object> existingStep = service.getFlowStepById(stepId);
if (existingStep != null) {
int flowDefId = toInt(existingStep.get("flow_definition_id"), 0);
if (service.getFlowDefinitionById(flowDefId, companyCode) == null) {
return ResponseEntity.status(403)
.body(ApiResponse.error("Access denied: flow does not belong to your company"));
}
}
boolean deleted = service.deleteFlowStep(stepId);
if (!deleted) {
return ResponseEntity.status(404).body(ApiResponse.error("Flow step not found"));
}
return ResponseEntity.ok(ApiResponse.success(null, "Flow step deleted successfully"));
}
// ══════════════════════════════════════════════════════════
// Connections
// ══════════════════════════════════════════════════════════
@GetMapping("/connections/{flowId}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowConnectionList(
@PathVariable("flowId") int flowId) {
return ResponseEntity.ok(ApiResponse.success(service.getFlowConnectionList(flowId)));
}
@PostMapping("/connections")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertFlowConnection(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
Object flowDefIdRaw = body.get("flowDefinitionId");
Object fromRaw = body.get("fromStepId");
Object toRaw = body.get("toStepId");
if (flowDefIdRaw == null || fromRaw == null || toRaw == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("flowDefinitionId, fromStepId, and toStepId are required"));
}
int flowDefId = toInt(flowDefIdRaw, 0);
int fromStepId = toInt(fromRaw, 0);
int toStepId = toInt(toRaw, 0);
// 플로우 소유권 확인
if (service.getFlowDefinitionById(flowDefId, companyCode) == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("Flow definition not found or access denied"));
}
// 스텝 소속 검증
Map<String, Object> fromStep = service.getFlowStepById(fromStepId);
Map<String, Object> toStep = service.getFlowStepById(toStepId);
if (fromStep == null || !String.valueOf(fromStep.get("flow_definition_id")).equals(String.valueOf(flowDefId))
|| toStep == null || !String.valueOf(toStep.get("flow_definition_id")).equals(String.valueOf(flowDefId))) {
return ResponseEntity.status(400)
.body(ApiResponse.error("fromStepId and toStepId must belong to the specified flow"));
}
try {
return ResponseEntity.ok(ApiResponse.success(service.insertFlowConnection(body)));
} catch (Exception e) {
log.error("플로우 연결 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to create connection"));
}
}
@DeleteMapping("/connections/{connectionId}")
public ResponseEntity<ApiResponse<Void>> deleteFlowConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("connectionId") int connectionId) {
Map<String, Object> existingConn = service.getFlowConnectionById(connectionId);
if (existingConn != null) {
int flowDefId = toInt(existingConn.get("flow_definition_id"), 0);
if (service.getFlowDefinitionById(flowDefId, companyCode) == null) {
return ResponseEntity.status(403)
.body(ApiResponse.error("Access denied: flow does not belong to your company"));
}
}
boolean deleted = service.deleteFlowConnection(connectionId);
if (!deleted) {
return ResponseEntity.status(404).body(ApiResponse.error("Connection not found"));
}
return ResponseEntity.ok(ApiResponse.success(null, "Connection deleted successfully"));
}
// ══════════════════════════════════════════════════════════
// Execution
// ══════════════════════════════════════════════════════════
@GetMapping("/{flowId}/step/{stepId}/count")
public ResponseEntity<ApiResponse<Map<String, Object>>> getFlowStepDataCount(
@PathVariable("flowId") int flowId,
@PathVariable("stepId") int stepId) {
try {
return ResponseEntity.ok(ApiResponse.success(service.getFlowStepDataCount(flowId, stepId)));
} catch (Exception e) {
log.error("스텝 카운트 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to get step data count"));
}
}
@GetMapping("/{flowId}/step/{stepId}/list")
public ResponseEntity<ApiResponse<Object>> getFlowStepDataList(
@PathVariable("flowId") int flowId,
@PathVariable("stepId") int stepId,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int pageSize) {
try {
return ResponseEntity.ok(ApiResponse.success(
service.getFlowStepDataList(flowId, stepId, page, pageSize)));
} catch (Exception e) {
log.error("스텝 데이터 목록 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to get step data list"));
}
}
@GetMapping("/{flowId}/step/{stepId}/column-labels")
public ResponseEntity<ApiResponse<Map<String, Object>>> getFlowColumnLabelList(
@PathVariable("flowId") int flowId,
@PathVariable("stepId") int stepId) {
try {
Map<String, Object> step = service.getFlowStepById(stepId);
if (step == null) {
return ResponseEntity.status(404).body(ApiResponse.error("Step not found"));
}
Map<String, Object> flow = service.getFlowDefinitionById(flowId, "*");
if (flow == null) {
return ResponseEntity.status(404).body(ApiResponse.error("Flow definition not found"));
}
Map<String, Object> labels = service.getFlowColumnLabelList(flowId, stepId);
return ResponseEntity.ok(ApiResponse.success(labels != null ? labels : Map.of()));
} catch (Exception e) {
log.error("컬럼 라벨 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to get step column labels"));
}
}
@GetMapping("/{flowId}/steps/counts")
public ResponseEntity<ApiResponse<Map<String, Object>>> getFlowStepCountList(
@PathVariable("flowId") int flowId) {
try {
return ResponseEntity.ok(ApiResponse.success(service.getFlowStepCountList(flowId)));
} catch (Exception e) {
log.error("전체 스텝 카운트 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to get all step counts"));
}
}
@PutMapping("/{flowId}/step/{stepId}/data/{recordId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateFlowStepData(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("flowId") int flowId,
@PathVariable("stepId") int stepId,
@PathVariable("recordId") String recordId,
@RequestBody Map<String, Object> updateData) {
if (updateData == null || updateData.isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error("Update data is required"));
}
try {
Map<String, Object> result =
service.updateFlowStepData(flowId, stepId, recordId, updateData, userId, companyCode);
return ResponseEntity.ok(ApiResponse.success(result, "Data updated successfully"));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("스텝 데이터 수정 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to update step data"));
}
}
// ══════════════════════════════════════════════════════════
// Data Move
// ══════════════════════════════════════════════════════════
@PostMapping("/move")
public ResponseEntity<ApiResponse<Void>> moveData(
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
Object flowId = body.get("flowId");
Object fromStepId = body.get("fromStepId");
Object recordId = body.get("recordId");
Object toStepId = body.get("toStepId");
if (flowId == null || fromStepId == null || recordId == null || toStepId == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("flowId, fromStepId, recordId, and toStepId are required"));
}
try {
service.moveDataToStep(
toInt(flowId, 0), toInt(fromStepId, 0), toInt(toStepId, 0),
recordId.toString(), userId, (String) body.get("note"));
return ResponseEntity.ok(ApiResponse.success(null, "Data moved successfully"));
} catch (Exception e) {
log.error("데이터 이동 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to move data"));
}
}
@PostMapping("/move-batch")
public ResponseEntity<ApiResponse<Map<String, Object>>> moveBatchData(
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
Object flowId = body.get("flowId");
Object fromStepId = body.get("fromStepId");
Object toStepId = body.get("toStepId");
Object dataIds = body.get("dataIds");
if (flowId == null || fromStepId == null || toStepId == null
|| !(dataIds instanceof List)) {
return ResponseEntity.status(400)
.body(ApiResponse.error(
"flowId, fromStepId, toStepId, and dataIds (array) are required"));
}
@SuppressWarnings("unchecked")
List<Object> ids = (List<Object>) dataIds;
try {
Map<String, Object> result = service.moveBatchData(
toInt(flowId, 0), toInt(fromStepId, 0), toInt(toStepId, 0), ids, userId);
long successCount = ids.stream()
.filter(id -> result.get("results") instanceof List<?> list
&& list.stream().anyMatch(r ->
r instanceof Map<?, ?> m
&& Boolean.TRUE.equals(m.get("success"))
&& id.toString().equals(String.valueOf(m.get("id")))))
.count();
long failureCount = ids.size() - successCount;
boolean overallSuccess = (boolean) result.get("success");
String message = overallSuccess
? successCount + "건의 데이터를 성공적으로 이동했습니다"
: successCount + "건 성공, " + failureCount + "건 실패";
Map<String, Object> data = new java.util.LinkedHashMap<>();
data.put("successCount", successCount);
data.put("failureCount", failureCount);
data.put("total", ids.size());
result.put("message", message);
result.put("data", data);
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("배치 데이터 이동 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to move batch data"));
}
}
// ══════════════════════════════════════════════════════════
// Audit Logs
// ══════════════════════════════════════════════════════════
/** GET /audit/:flowId — 플로우 전체 이력 */
@GetMapping("/audit/{flowId}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowAuditLogList(
@PathVariable("flowId") int flowId,
@RequestParam(defaultValue = "100") int limit) {
try {
return ResponseEntity.ok(ApiResponse.success(service.getFlowAuditLogList(flowId, limit)));
} catch (Exception e) {
log.error("플로우 감사 로그 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to get flow audit logs"));
}
}
/** GET /audit/:flowId/:recordId — 특정 레코드 이력 */
@GetMapping("/audit/{flowId}/{recordId}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowAuditLogListByRecord(
@PathVariable("flowId") int flowId,
@PathVariable("recordId") String recordId) {
try {
return ResponseEntity.ok(ApiResponse.success(service.getFlowAuditLogListByRecord(flowId, recordId)));
} catch (Exception e) {
log.error("감사 로그 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "Failed to get audit logs"));
}
}
// ══════════════════════════════════════════════════════════
// Procedures
// ══════════════════════════════════════════════════════════
@GetMapping("/procedures")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowProcedureList(
@RequestParam(required = false) String dbSource,
@RequestParam(required = false) Integer connectionId,
@RequestParam(required = false) String schema) {
if (dbSource != null && !"internal".equals(dbSource) && !"external".equals(dbSource)) {
return ResponseEntity.status(400)
.body(ApiResponse.error("dbSource는 internal 또는 external이어야 합니다"));
}
if ("external".equals(dbSource) && connectionId == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("외부 DB 조회 시 connectionId가 필요합니다"));
}
try {
// 현재 구현은 internal DB만 지원 (external은 별도 연결 관리가 필요)
return ResponseEntity.ok(ApiResponse.success(service.getFlowProcedureList(schema)));
} catch (Exception e) {
log.error("프로시저 목록 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "프로시저 목록 조회에 실패했습니다"));
}
}
@GetMapping("/procedures/{name}/parameters")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowProcedureParameterList(
@PathVariable("name") String name,
@RequestParam(required = false) String dbSource,
@RequestParam(required = false) Integer connectionId,
@RequestParam(required = false) String schema) {
if (name == null || name.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("프로시저 이름이 필요합니다"));
}
try {
return ResponseEntity.ok(ApiResponse.success(
service.getFlowProcedureParameterList(name, schema)));
} catch (Exception e) {
log.error("프로시저 파라미터 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
: "프로시저 파라미터 조회에 실패했습니다"));
}
}
// ── private helper ────────────────────────────────────────
private int toInt(Object val, int defaultVal) {
if (val == null) return defaultVal;
try { return Integer.parseInt(val.toString()); }
catch (NumberFormatException e) { return defaultVal; }
}
}
@@ -0,0 +1,196 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.FlowExternalDbConnectionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 플로우 전용 외부 DB 연결 컨트롤러
*
* GET /api/flow-external-db - 목록 (?activeOnly=true)
* GET /api/flow-external-db/{id} - 단건
* POST /api/flow-external-db - 생성
* PUT /api/flow-external-db/{id} - 수정
* DELETE /api/flow-external-db/{id} - 삭제
* POST /api/flow-external-db/{id}/test - 연결 테스트
* GET /api/flow-external-db/{id}/tables - 테이블 목록
* GET /api/flow-external-db/{id}/tables/{tableName}/columns - 컬럼 목록
*/
@RestController
@RequestMapping("/api/flow-external-db")
@RequiredArgsConstructor
@Slf4j
public class FlowExternalDbConnectionController {
private final FlowExternalDbConnectionService service;
@GetMapping
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAll(
@RequestAttribute(value = "companyCode", required = false) String companyCode,
@RequestParam(required = false, defaultValue = "false") String activeOnly) {
try {
boolean active = "true".equalsIgnoreCase(activeOnly);
List<Map<String, Object>> connections = service.findAll(active);
return ResponseEntity.ok(ApiResponse.success(connections,
connections.size() + "개의 외부 DB 연결을 조회했습니다"));
} catch (Exception e) {
log.error("외부 DB 연결 목록 조회 오류", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("외부 DB 연결 목록 조회 중 오류가 발생했습니다"));
}
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getById(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id) {
try {
Map<String, Object> conn = service.findById(id);
if (conn == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("외부 DB 연결을 찾을 수 없습니다"));
}
return ResponseEntity.ok(ApiResponse.success(conn));
} catch (Exception e) {
log.error("외부 DB 연결 조회 오류: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("외부 DB 연결 조회 중 오류가 발생했습니다"));
}
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> create(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
// 필수 필드 검증
if (isBlank(body, "name") || isBlank(body, "dbType") || isBlank(body, "db_type") && isBlank(body, "dbType")
|| isBlank(body, "host") || body.get("port") == null
|| isBlank(body, "databaseName") && isBlank(body, "database_name")
|| isBlank(body, "username") || isBlank(body, "password")) {
return ResponseEntity.status(400)
.body(ApiResponse.error("필수 필드가 누락되었습니다"));
}
try {
String userId = companyCode != null ? companyCode : "system";
Map<String, Object> conn = service.create(body, userId);
log.info("외부 DB 연결 생성: {}", conn.get("name"));
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(conn, "외부 DB 연결이 생성되었습니다"));
} catch (Exception e) {
log.error("외부 DB 연결 생성 오류", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("외부 DB 연결 생성 중 오류가 발생했습니다"));
}
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> update(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id,
@RequestBody Map<String, Object> body) {
try {
String userId = companyCode != null ? companyCode : "system";
Map<String, Object> conn = service.update(id, body, userId);
if (conn == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("외부 DB 연결을 찾을 수 없습니다"));
}
log.info("외부 DB 연결 수정: id={}, name={}", id, conn.get("name"));
return ResponseEntity.ok(ApiResponse.success(conn, "외부 DB 연결이 수정되었습니다"));
} catch (Exception e) {
log.error("외부 DB 연결 수정 오류: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("외부 DB 연결 수정 중 오류가 발생했습니다"));
}
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> delete(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id) {
try {
boolean deleted = service.delete(id);
if (!deleted) {
return ResponseEntity.status(404)
.body(ApiResponse.error("외부 DB 연결을 찾을 수 없습니다"));
}
log.info("외부 DB 연결 삭제: id={}", id);
return ResponseEntity.ok(ApiResponse.success(null, "외부 DB 연결이 삭제되었습니다"));
} catch (Exception e) {
log.error("외부 DB 연결 삭제 오류: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("외부 DB 연결 삭제 중 오류가 발생했습니다"));
}
}
@PostMapping("/{id}/test")
public ResponseEntity<ApiResponse<Void>> testConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id) {
try {
Map<String, Object> result = service.testConnection(id);
boolean success = Boolean.TRUE.equals(result.get("success"));
String message = (String) result.get("message");
if (success) {
log.info("외부 DB 연결 테스트 성공: id={}", id);
return ResponseEntity.ok(ApiResponse.success(null, message));
} else {
log.warn("외부 DB 연결 테스트 실패: id={}, msg={}", id, message);
return ResponseEntity.status(400).body(ApiResponse.error(message));
}
} catch (Exception e) {
log.error("외부 DB 연결 테스트 오류: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("외부 DB 연결 테스트 중 오류가 발생했습니다"));
}
}
@GetMapping("/{id}/tables")
public ResponseEntity<?> getTables(
@RequestAttribute(value = "companyCode", required = false) String companyCode,
@PathVariable int id) {
try {
Map<String, Object> result = service.getTables(id);
if (Boolean.TRUE.equals(result.get("success"))) {
return ResponseEntity.ok(result);
}
return ResponseEntity.status(400).body(result);
} catch (Exception e) {
log.error("외부 DB 테이블 목록 조회 오류: id={}", id, e);
return ResponseEntity.status(500).body(Map.of(
"success", false, "message", "외부 DB 테이블 목록 조회 중 오류가 발생했습니다"));
}
}
@GetMapping("/{id}/tables/{tableName}/columns")
public ResponseEntity<?> getTableColumns(
@RequestAttribute(value = "companyCode", required = false) String companyCode,
@PathVariable int id,
@PathVariable String tableName) {
try {
Map<String, Object> result = service.getTableColumns(id, tableName);
if (Boolean.TRUE.equals(result.get("success"))) {
return ResponseEntity.ok(result);
}
return ResponseEntity.status(400).body(result);
} catch (Exception e) {
log.error("외부 DB 컬럼 목록 조회 오류: id={}, table={}", id, tableName, e);
return ResponseEntity.status(500).body(Map.of(
"success", false, "message", "외부 DB 컬럼 목록 조회 중 오류가 발생했습니다"));
}
}
// ── 유효성 헬퍼 ───────────────────────────────────────────────
private boolean isBlank(Map<String, Object> body, String key) {
Object v = body.get(key);
return v == null || v.toString().isBlank();
}
}
@@ -0,0 +1,170 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.LayoutService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/layouts")
@RequiredArgsConstructor
@Slf4j
public class LayoutController {
private final LayoutService layoutService;
/**
* GET /api/layouts
* 레이아웃 목록 조회
* @param params page, size, category, layoutType, searchTerm, includePublic
*/
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getLayouts(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(layoutService.getLayouts(params)));
}
/**
* GET /api/layouts/counts-by-category
* 카테고리별 레이아웃 개수 조회
* ※ /{id} 보다 먼저 선언해야 Spring MVC 가 정확히 매핑함
*/
@GetMapping("/counts-by-category")
public ResponseEntity<ApiResponse<Map<String, Object>>> getLayoutCountsByCategory(
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
layoutService.getLayoutCountsByCategory(companyCode)));
}
/**
* GET /api/layouts/:id
* 레이아웃 상세 조회
*/
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getLayoutById(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("id") String layoutCode) {
Map<String, Object> layout = layoutService.getLayoutById(layoutCode, companyCode);
if (layout == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("레이아웃을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(layout));
}
/**
* POST /api/layouts
* 레이아웃 생성
*/
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createLayout(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
if (body.get("layoutName") == null || body.get("layoutType") == null || body.get("category") == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("필수 필드가 누락되었습니다. (layoutName, layoutType, category)"));
}
if (body.get("layoutConfig") == null || body.get("zonesConfig") == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("레이아웃 설정과 존 설정은 필수입니다."));
}
try {
Map<String, Object> created = layoutService.createLayout(body, companyCode, userId);
return ResponseEntity.status(201)
.body(ApiResponse.success(created, "레이아웃이 성공적으로 생성되었습니다."));
} catch (Exception e) {
log.error("레이아웃 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("레이아웃 생성에 실패했습니다."));
}
}
/**
* PUT /api/layouts/:id
* 레이아웃 수정
*/
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateLayout(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("id") String layoutCode,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> updated = layoutService.updateLayout(layoutCode, body, companyCode, userId);
if (updated == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("레이아웃을 찾을 수 없거나 수정 권한이 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(updated, "레이아웃이 성공적으로 수정되었습니다."));
} catch (Exception e) {
log.error("레이아웃 수정 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("레이아웃 수정에 실패했습니다."));
}
}
/**
* DELETE /api/layouts/:id
* 레이아웃 삭제 (소프트)
*/
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteLayout(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("id") String layoutCode) {
try {
layoutService.deleteLayout(layoutCode, companyCode, userId);
return ResponseEntity.ok(ApiResponse.success(null, "레이아웃이 성공적으로 삭제되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404)
.body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("레이아웃 삭제 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("레이아웃 삭제에 실패했습니다."));
}
}
/**
* POST /api/layouts/:id/duplicate
* 레이아웃 복제
*/
@PostMapping("/{id}/duplicate")
public ResponseEntity<ApiResponse<Map<String, Object>>> duplicateLayout(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("id") String layoutCode,
@RequestBody Map<String, Object> body) {
String newName = (String) body.get("newName");
if (newName == null || newName.isBlank()) {
return ResponseEntity.status(400)
.body(ApiResponse.error("새 레이아웃 이름이 필요합니다."));
}
try {
Map<String, Object> duplicated = layoutService.duplicateLayout(
layoutCode, newName, companyCode, userId);
return ResponseEntity.status(201)
.body(ApiResponse.success(duplicated, "레이아웃이 성공적으로 복제되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404)
.body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("레이아웃 복제 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("레이아웃 복제에 실패했습니다."));
}
}
}
@@ -0,0 +1,63 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.MailAccountFileService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/mail-account-file")
@RequiredArgsConstructor
@Slf4j
public class MailAccountFileController {
private final MailAccountFileService mailAccountFileService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMailAccountFileList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(mailAccountFileService.getMailAccountFileList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMailAccountFileInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(mailAccountFileService.getMailAccountFileInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertMailAccountFile(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(mailAccountFileService.insertMailAccountFile(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateMailAccountFile(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(mailAccountFileService.updateMailAccountFile(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteMailAccountFile(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(mailAccountFileService.deleteMailAccountFile(params)));
}
}
@@ -0,0 +1,118 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.MailReceiveBasicService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/mail-receive-basic")
@RequiredArgsConstructor
@Slf4j
public class MailReceiveBasicController {
private final MailReceiveBasicService mailReceiveBasicService;
/** GET /today-count → 오늘 수신 메일 수 */
@GetMapping("/today-count")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMailReceiveBasicTodayCount(
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
int count = mailReceiveBasicService.getMailReceiveBasicTodayCount(params);
Map<String, Object> data = new HashMap<>();
data.put("count", count);
return ResponseEntity.ok(ApiResponse.success(data));
}
/** GET /{accountId}/{seqno}/attachment/{index} → 첨부파일 다운로드 */
@GetMapping("/{accountId}/{seqno}/attachment/{index}")
public ResponseEntity<byte[]> downloadMailReceiveBasicAttachment(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long accountId,
@PathVariable String seqno,
@PathVariable int index) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("accountId", accountId);
params.put("seqno", seqno);
params.put("attachIndex", index);
Map<String, Object> attachment = mailReceiveBasicService.getMailReceiveBasicAttachment(params);
if (attachment == null) {
return ResponseEntity.notFound().build();
}
byte[] data = (byte[]) attachment.get("contentData");
String fileName = (String) attachment.getOrDefault("fileName", "attachment");
String contentType = (String) attachment.getOrDefault("contentType", "application/octet-stream");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
.contentType(MediaType.parseMediaType(contentType))
.body(data);
}
/** POST /{accountId}/{seqno}/mark-read → 메일 읽음 처리 */
@PostMapping("/{accountId}/{seqno}/mark-read")
public ResponseEntity<ApiResponse<Map<String, Object>>> markMailReceiveBasicAsRead(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long accountId,
@PathVariable String seqno) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("accountId", accountId);
params.put("seqno", seqno);
return ResponseEntity.ok(ApiResponse.success(mailReceiveBasicService.markMailReceiveBasicAsRead(params)));
}
/** DELETE /{accountId}/{seqno} → 메일 삭제 */
@DeleteMapping("/{accountId}/{seqno}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteMailReceiveBasic(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long accountId,
@PathVariable String seqno) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("accountId", accountId);
params.put("seqno", seqno);
return ResponseEntity.ok(ApiResponse.success(mailReceiveBasicService.deleteMailReceiveBasic(params)));
}
/** GET /{accountId}/{seqno} → 메일 상세 조회 */
@GetMapping("/{accountId}/{seqno}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMailReceiveBasicInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long accountId,
@PathVariable String seqno) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("accountId", accountId);
params.put("seqno", seqno);
return ResponseEntity.ok(ApiResponse.success(mailReceiveBasicService.getMailReceiveBasicInfo(params)));
}
/** POST /{accountId}/test-imap → IMAP 연결 테스트 */
@PostMapping("/{accountId}/test-imap")
public ResponseEntity<ApiResponse<Map<String, Object>>> testMailReceiveBasicImapConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long accountId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("accountId", accountId);
return ResponseEntity.ok(ApiResponse.success(mailReceiveBasicService.testMailReceiveBasicImapConnection(params)));
}
/** GET /{accountId} → 메일 목록 조회 */
@GetMapping("/{accountId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMailReceiveBasicList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long accountId,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
params.put("accountId", accountId);
return ResponseEntity.ok(ApiResponse.success(mailReceiveBasicService.getMailReceiveBasicList(params)));
}
}
@@ -0,0 +1,97 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.MailSendSimpleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
/**
* 메일 발송 API
* Node.js: app.use("/api/mail/send", mailSendSimpleRoutes)
*/
@RestController
@RequestMapping("/api/mail/send")
@RequiredArgsConstructor
@Slf4j
public class MailSendSimpleController {
private final MailSendSimpleService mailSendSimpleService;
/**
* POST /api/mail/send/simple — 단건 메일 발송 (첨부파일 지원)
* multipart/form-data: accountId, to(JSON), cc(JSON), bcc(JSON),
* subject, templateId, customHtml, variables(JSON),
* modifiedTemplateComponents(JSON), fileNames(JSON), attachments[]
*/
@PostMapping(value = "/simple",
consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public ResponseEntity<ApiResponse<Map<String, Object>>> sendMail(
@RequestAttribute("companyCode") String companyCode,
@RequestParam(required = false) String accountId,
@RequestParam(required = false) String templateId,
@RequestParam(required = false) String modifiedTemplateComponents,
@RequestParam(required = false) String to,
@RequestParam(required = false) String cc,
@RequestParam(required = false) String bcc,
@RequestParam(required = false) String subject,
@RequestParam(required = false) String variables,
@RequestParam(required = false) String customHtml,
@RequestParam(required = false) String fileNames,
@RequestPart(value = "attachments", required = false) List<MultipartFile> attachments) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("companyCode", companyCode);
params.put("accountId", accountId);
params.put("templateId", templateId);
params.put("modifiedTemplateComponents", modifiedTemplateComponents);
params.put("to", to);
params.put("cc", cc);
params.put("bcc", bcc);
params.put("subject", subject);
params.put("variables", variables);
params.put("customHtml", customHtml);
params.put("fileNames", fileNames);
params.put("attachments", attachments);
return ResponseEntity.ok(ApiResponse.success(
mailSendSimpleService.sendMail(params), "메일이 발송되었습니다."));
}
/**
* POST /api/mail/send/bulk — 대량 메일 발송
* Body: { accountId, subject, templateId?, customHtml?,
* recipients: [{ email, variables? }] }
*/
@PostMapping("/bulk")
public ResponseEntity<ApiResponse<Map<String, Object>>> sendBulkMail(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
Map<String, Object> result = mailSendSimpleService.sendBulkMail(body);
int successCount = ((Number) result.getOrDefault("success", 0)).intValue();
int total = ((Number) result.getOrDefault("total", 0)).intValue();
return ResponseEntity.ok(ApiResponse.success(result,
successCount + "/" + total + " 건 발송 완료"));
}
/**
* POST /api/mail/send/test-connection — SMTP 연결 테스트
* Body: { accountId }
*/
@PostMapping("/test-connection")
public ResponseEntity<ApiResponse<Map<String, Object>>> testConnection(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
mailSendSimpleService.testConnection(body)));
}
}
@@ -0,0 +1,131 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.MailSentHistoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/mail-sent-history")
@RequiredArgsConstructor
@Slf4j
public class MailSentHistoryController {
private final MailSentHistoryService mailSentHistoryService;
// GET /statistics — /{id} 보다 먼저 정의해야 매핑 우선순위 확보
@GetMapping("/statistics")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMailSentHistoryStatistics(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(mailSentHistoryService.getMailSentHistoryStatistics(params)));
}
// GET / — 목록 조회 (페이지네이션)
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getMailSentHistoryList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(mailSentHistoryService.getMailSentHistoryList(params)));
}
// POST /draft — 임시 저장
@PostMapping("/draft")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveMailSentHistoryDraft(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(
mailSentHistoryService.saveMailSentHistoryDraft(body), "임시 저장되었습니다."));
}
// PUT /draft/{id} — 임시 저장 업데이트
@PutMapping("/draft/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateMailSentHistoryDraft(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(
mailSentHistoryService.updateMailSentHistoryDraft(body), "임시 저장이 업데이트되었습니다."));
}
// POST /bulk/delete — 일괄 소프트 삭제
@PostMapping("/bulk/delete")
public ResponseEntity<ApiResponse<Map<String, Object>>> bulkDeleteMailSentHistory(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(mailSentHistoryService.bulkDeleteMailSentHistory(body)));
}
// POST /bulk/permanent-delete — 일괄 영구 삭제
@PostMapping("/bulk/permanent-delete")
public ResponseEntity<ApiResponse<Map<String, Object>>> bulkPermanentDeleteMailSentHistory(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(mailSentHistoryService.bulkPermanentDeleteMailSentHistory(body)));
}
// POST /bulk/restore — 일괄 복구
@PostMapping("/bulk/restore")
public ResponseEntity<ApiResponse<Map<String, Object>>> bulkRestoreMailSentHistory(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(mailSentHistoryService.bulkRestoreMailSentHistory(body)));
}
// POST /{id}/restore — 메일 복구
@PostMapping("/{id}/restore")
public ResponseEntity<ApiResponse<Map<String, Object>>> restoreMailSentHistory(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(
mailSentHistoryService.restoreMailSentHistory(params), "메일이 복구되었습니다."));
}
// DELETE /{id}/permanent — 영구 삭제
@DeleteMapping("/{id}/permanent")
public ResponseEntity<ApiResponse<Map<String, Object>>> permanentlyDeleteMailSentHistory(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(
mailSentHistoryService.permanentlyDeleteMailSentHistory(params), "메일이 영구 삭제되었습니다."));
}
// GET /{id} — 단건 조회
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMailSentHistoryInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(mailSentHistoryService.getMailSentHistoryInfo(params)));
}
// DELETE /{id} — 소프트 삭제
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteMailSentHistory(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(
mailSentHistoryService.deleteMailSentHistory(params), "발송 이력이 삭제되었습니다."));
}
}
@@ -0,0 +1,100 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.MailTemplateFileService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/mail-template-file")
@RequiredArgsConstructor
@Slf4j
public class MailTemplateFileController {
private final MailTemplateFileService mailTemplateFileService;
/** GET /list → 템플릿 목록 조회 (alias) */
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMailTemplateFileListAlias(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(mailTemplateFileService.getMailTemplateFileList(params)));
}
/** GET / → 템플릿 목록 조회 */
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getMailTemplateFileList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(mailTemplateFileService.getMailTemplateFileList(params)));
}
/** GET /{id} → 템플릿 상세 조회 */
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMailTemplateFileInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(mailTemplateFileService.getMailTemplateFileInfo(params)));
}
/** POST / → 템플릿 생성 */
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertMailTemplateFile(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(mailTemplateFileService.insertMailTemplateFile(body)));
}
/** PUT /{id} → 템플릿 수정 */
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateMailTemplateFile(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(mailTemplateFileService.updateMailTemplateFile(body)));
}
/** DELETE /{id} → 템플릿 삭제 */
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteMailTemplateFile(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(mailTemplateFileService.deleteMailTemplateFile(params)));
}
/** POST /{id}/preview → 템플릿 HTML 미리보기 */
@PostMapping("/{id}/preview")
public ResponseEntity<ApiResponse<Map<String, Object>>> previewMailTemplateFile(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
Map<String, Object> result = mailTemplateFileService.previewMailTemplateFile(body);
if (result.isEmpty()) {
return ResponseEntity.status(404).body(ApiResponse.error("템플릿을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
/** POST /{id}/preview-with-query → 쿼리 연동 미리보기 (미지원) */
@PostMapping("/{id}/preview-with-query")
public ResponseEntity<ApiResponse<Map<String, Object>>> previewMailTemplateFileWithQuery(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
return ResponseEntity.status(501).body(ApiResponse.error("SQL 쿼리 연동 기능은 현재 지원하지 않습니다."));
}
}
@@ -0,0 +1,72 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.MapDataService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/map-data")
@RequiredArgsConstructor
@Slf4j
public class MapDataController {
private final MapDataService mapDataService;
/**
* 외부 DB에서 지도 데이터 조회
* GET /api/map-data/external/:connectionId
* query params: tableName(필수), latColumn(필수), lngColumn(필수),
* labelColumn, statusColumn, additionalColumns(쉼표구분), whereClause
*/
@GetMapping("/external/{connectionId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getExternalMapData(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long connectionId,
@RequestParam Map<String, Object> params) {
String tableName = (String) params.get("tableName");
String latColumn = (String) params.get("latColumn");
String lngColumn = (String) params.get("lngColumn");
if (tableName == null || tableName.isBlank()
|| latColumn == null || latColumn.isBlank()
|| lngColumn == null || lngColumn.isBlank()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("tableName, latColumn, lngColumn은 필수입니다."));
}
Map<String, Object> data = mapDataService.getExternalMapData(connectionId, params);
return ResponseEntity.ok(ApiResponse.success(data));
}
/**
* 내부 DB에서 지도 데이터 조회
* GET /api/map-data/internal
* query params: tableName(필수), latColumn(필수), lngColumn(필수),
* labelColumn, statusColumn, additionalColumns(쉼표구분), whereClause
*/
@GetMapping("/internal")
public ResponseEntity<ApiResponse<Map<String, Object>>> getInternalMapData(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
String tableName = (String) params.get("tableName");
String latColumn = (String) params.get("latColumn");
String lngColumn = (String) params.get("lngColumn");
if (tableName == null || tableName.isBlank()
|| latColumn == null || latColumn.isBlank()
|| lngColumn == null || lngColumn.isBlank()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("tableName, latColumn, lngColumn은 필수입니다."));
}
Map<String, Object> data = mapDataService.getInternalMapData(params);
return ResponseEntity.ok(ApiResponse.success(data));
}
}
@@ -0,0 +1,227 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.MoldService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/mold")
@RequiredArgsConstructor
@Slf4j
public class MoldController {
private final MoldService moldService;
// ── 금형 마스터 ───────────────────────────────────────────────────────────
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getMoldList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(moldService.getMoldList(params)));
}
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMoldListAlias(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(moldService.getMoldList(params)));
}
@GetMapping("/{moldCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMoldDetail(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String moldCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("moldCode", moldCode);
Map<String, Object> result = moldService.getMoldInfo(params);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("금형을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createMold(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("writer", userId);
if (body.get("moldCode") == null || body.get("moldName") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("금형코드와 금형명은 필수입니다."));
}
try {
return ResponseEntity.ok(ApiResponse.success(moldService.insertMold(body), "금형이 등록되었습니다."));
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("unique")) {
return ResponseEntity.status(409).body(ApiResponse.error("이미 존재하는 금형코드입니다."));
}
return ResponseEntity.status(500).body(ApiResponse.error(e.getMessage()));
}
}
@PutMapping("/{moldCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateMold(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String moldCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("moldCode", moldCode);
Map<String, Object> result = moldService.updateMold(body);
if (result == null) return ResponseEntity.status(404).body(ApiResponse.error("금형을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(result, "금형이 수정되었습니다."));
}
@DeleteMapping("/{moldCode}")
public ResponseEntity<ApiResponse<Void>> deleteMold(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String moldCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("moldCode", moldCode);
if (!moldService.deleteMold(params)) {
return ResponseEntity.status(404).body(ApiResponse.error("금형을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "금형이 삭제되었습니다."));
}
// ── 일련번호 ──────────────────────────────────────────────────────────────
@GetMapping("/{moldCode}/serials")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMoldSerials(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String moldCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("moldCode", moldCode);
return ResponseEntity.ok(ApiResponse.success(moldService.getMoldSerialList(params)));
}
@PostMapping("/{moldCode}/serials")
public ResponseEntity<ApiResponse<Map<String, Object>>> createMoldSerial(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable String moldCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("moldCode", moldCode);
body.put("writer", userId);
try {
return ResponseEntity.ok(ApiResponse.success(moldService.insertMoldSerial(body), "일련번호가 등록되었습니다."));
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("unique")) {
return ResponseEntity.status(409).body(ApiResponse.error("이미 존재하는 일련번호입니다."));
}
return ResponseEntity.status(500).body(ApiResponse.error(e.getMessage()));
}
}
@DeleteMapping("/serials/{id}")
public ResponseEntity<ApiResponse<Void>> deleteMoldSerial(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
if (!moldService.deleteMoldSerial(params)) {
return ResponseEntity.status(404).body(ApiResponse.error("일련번호를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "일련번호가 삭제되었습니다."));
}
@GetMapping("/{moldCode}/serial-summary")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMoldSerialSummary(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String moldCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("moldCode", moldCode);
return ResponseEntity.ok(ApiResponse.success(moldService.getMoldSerialSummary(params)));
}
// ── 점검항목 ──────────────────────────────────────────────────────────────
@GetMapping("/{moldCode}/inspections")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMoldInspections(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String moldCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("moldCode", moldCode);
return ResponseEntity.ok(ApiResponse.success(moldService.getMoldInspectionList(params)));
}
@PostMapping("/{moldCode}/inspections")
public ResponseEntity<ApiResponse<Map<String, Object>>> createMoldInspection(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable String moldCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("moldCode", moldCode);
body.put("writer", userId);
if (body.get("inspectionItem") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("점검항목명은 필수입니다."));
}
return ResponseEntity.ok(ApiResponse.success(moldService.insertMoldInspection(body), "점검항목이 등록되었습니다."));
}
@DeleteMapping("/inspections/{id}")
public ResponseEntity<ApiResponse<Void>> deleteMoldInspection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
if (!moldService.deleteMoldInspection(params)) {
return ResponseEntity.status(404).body(ApiResponse.error("점검항목을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "점검항목이 삭제되었습니다."));
}
// ── 부품 ──────────────────────────────────────────────────────────────────
@GetMapping("/{moldCode}/parts")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMoldParts(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String moldCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("moldCode", moldCode);
return ResponseEntity.ok(ApiResponse.success(moldService.getMoldPartList(params)));
}
@PostMapping("/{moldCode}/parts")
public ResponseEntity<ApiResponse<Map<String, Object>>> createMoldPart(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable String moldCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("moldCode", moldCode);
body.put("writer", userId);
if (body.get("partName") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("부품명은 필수입니다."));
}
return ResponseEntity.ok(ApiResponse.success(moldService.insertMoldPart(body), "부품이 등록되었습니다."));
}
@DeleteMapping("/parts/{id}")
public ResponseEntity<ApiResponse<Void>> deleteMoldPart(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
if (!moldService.deleteMoldPart(params)) {
return ResponseEntity.status(404).body(ApiResponse.error("부품을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "부품이 삭제되었습니다."));
}
}
@@ -0,0 +1,125 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.MultiConnectionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/multi-connection")
@RequiredArgsConstructor
@Slf4j
public class MultiConnectionController {
private final MultiConnectionService multiConnectionService;
/** GET /connections/:connectionId/tables */
@GetMapping("/connections/{connectionId}/tables")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTablesFromConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int connectionId) {
List<Map<String, Object>> tables = multiConnectionService.getTablesFromConnection(connectionId);
return ResponseEntity.ok(ApiResponse.success(tables,
"커넥션 " + connectionId + "의 테이블 목록을 조회했습니다."));
}
/** GET /connections/:connectionId/tables/batch */
@GetMapping("/connections/{connectionId}/tables/batch")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getBatchTablesWithColumns(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int connectionId) {
List<Map<String, Object>> tables = multiConnectionService.getBatchTablesWithColumns(connectionId);
return ResponseEntity.ok(ApiResponse.success(tables,
"커넥션 " + connectionId + "의 테이블 정보를 배치 조회했습니다."));
}
/** GET /connections/:connectionId/tables/:tableName/columns */
@GetMapping("/connections/{connectionId}/tables/{tableName}/columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getColumnsFromConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int connectionId,
@PathVariable String tableName) {
if (tableName == null || tableName.isBlank()) {
return ResponseEntity.badRequest().body(ApiResponse.error("테이블명이 입력되지 않았습니다."));
}
List<Map<String, Object>> columns = multiConnectionService.getColumnsFromConnection(connectionId, tableName);
return ResponseEntity.ok(ApiResponse.success(columns,
"테이블 " + tableName + "의 컬럼 정보를 조회했습니다."));
}
/** POST /connections/:connectionId/query */
@PostMapping("/connections/{connectionId}/query")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> fetchDataFromConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int connectionId,
@RequestBody Map<String, Object> body) {
if (body.get("tableName") == null) {
return ResponseEntity.badRequest().body(ApiResponse.error("테이블명이 입력되지 않았습니다."));
}
List<Map<String, Object>> data = multiConnectionService.fetchDataFromConnection(connectionId, body);
return ResponseEntity.ok(ApiResponse.success(data,
"데이터 조회가 완료되었습니다. (" + data.size() + "건)"));
}
/** POST /connections/:connectionId/insert */
@PostMapping("/connections/{connectionId}/insert")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertDataToConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int connectionId,
@RequestBody Map<String, Object> body) {
if (body.get("tableName") == null || body.get("data") == null) {
return ResponseEntity.badRequest().body(ApiResponse.error("테이블명과 데이터가 필요합니다."));
}
Map<String, Object> result = multiConnectionService.insertDataToConnection(connectionId, body);
return ResponseEntity.status(201).body(ApiResponse.success(result, "데이터 삽입이 완료되었습니다."));
}
/** PUT /connections/:connectionId/update */
@PutMapping("/connections/{connectionId}/update")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> updateDataToConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int connectionId,
@RequestBody Map<String, Object> body) {
if (body.get("tableName") == null || body.get("data") == null || body.get("conditions") == null) {
return ResponseEntity.badRequest().body(ApiResponse.error("테이블명, 데이터, 조건이 모두 필요합니다."));
}
List<Map<String, Object>> result = multiConnectionService.updateDataToConnection(connectionId, body);
return ResponseEntity.ok(ApiResponse.success(result,
"데이터 업데이트가 완료되었습니다. (" + result.size() + "건)"));
}
/** DELETE /connections/:connectionId/delete */
@DeleteMapping("/connections/{connectionId}/delete")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> deleteDataFromConnection(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int connectionId,
@RequestBody Map<String, Object> body) {
if (body.get("tableName") == null || body.get("conditions") == null) {
return ResponseEntity.badRequest().body(ApiResponse.error("테이블명과 삭제 조건이 필요합니다."));
}
List<Map<String, Object>> result = multiConnectionService.deleteDataFromConnection(connectionId, body);
return ResponseEntity.ok(ApiResponse.success(result,
"데이터 삭제가 완료되었습니다. (" + result.size() + "건)"));
}
/** POST /validate-self-operation */
@PostMapping("/validate-self-operation")
public ResponseEntity<ApiResponse<Map<String, Object>>> validateSelfOperation(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
if (body.get("tableName") == null || body.get("operation") == null || body.get("conditions") == null) {
return ResponseEntity.badRequest().body(ApiResponse.error("테이블명, 작업 타입, 조건이 모두 필요합니다."));
}
String op = body.get("operation").toString();
if (!op.equals("update") && !op.equals("delete")) {
return ResponseEntity.badRequest().body(ApiResponse.error("작업 타입은 'update' 또는 'delete'만 허용됩니다."));
}
Map<String, Object> result = multiConnectionService.validateSelfOperation(body);
return ResponseEntity.ok(ApiResponse.success(result, "검증이 완료되었습니다."));
}
}
@@ -0,0 +1,399 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.MultilangService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@Slf4j
@RestController
@RequestMapping("/api/multilang")
@RequiredArgsConstructor
public class MultilangController {
private final MultilangService multilangService;
// ─────────────────────────────────────────────────────────────────────────
// 배치 번역 (인증 불필요 — SecurityConfig에서 permitAll)
// ─────────────────────────────────────────────────────────────────────────
@PostMapping("/batch")
public ResponseEntity<ApiResponse<Map<String, String>>> getBatchTranslations(
@RequestBody Map<String, Object> body,
@RequestParam(required = false) String companyCode,
@RequestParam(required = false) String menuCode,
@RequestParam(required = false) String userLang) {
String finalCompanyCode = companyCode != null ? companyCode : str(body.get("companyCode"));
String finalMenuCode = menuCode != null ? menuCode : str(body.get("menuCode"));
String finalUserLang = userLang != null ? userLang : str(body.get("userLang"));
@SuppressWarnings("unchecked")
List<String> langKeys = (List<String>) body.get("langKeys");
if (langKeys == null || langKeys.isEmpty()) {
return ResponseEntity.badRequest().body(ApiResponse.error("langKeys 배열이 필요합니다."));
}
if (finalCompanyCode.isEmpty() || finalUserLang.isEmpty()) {
return ResponseEntity.badRequest().body(ApiResponse.error("companyCode와 userLang은 필수입니다."));
}
Map<String, String> translations =
multilangService.getBatchTranslations(finalCompanyCode, finalMenuCode, finalUserLang, langKeys);
return ResponseEntity.ok(ApiResponse.success(translations, "다국어 텍스트 배치 조회 성공"));
}
// ─────────────────────────────────────────────────────────────────────────
// 언어 관리
// ─────────────────────────────────────────────────────────────────────────
@GetMapping("/languages")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getLanguages() {
return ResponseEntity.ok(ApiResponse.success(
multilangService.getLanguages(), "언어 목록 조회 성공"));
}
@PostMapping("/languages")
public ResponseEntity<ApiResponse<Map<String, Object>>> createLanguage(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
String langCode = str(body.get("langCode"));
String langName = str(body.get("langName"));
String langNative = str(body.get("langNative"));
if (langCode.isEmpty() || langName.isEmpty() || langNative.isEmpty()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("언어 코드, 언어명, 원어명은 필수입니다."));
}
String userId = str(body.getOrDefault("createdBy", "system"));
body.put("createdBy", userId);
body.put("updatedBy", userId);
Map<String, Object> created = multilangService.createLanguage(body);
return ResponseEntity.status(201)
.body(ApiResponse.success(created, "언어가 성공적으로 생성되었습니다."));
}
@PutMapping("/languages/{langCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateLanguage(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String langCode,
@RequestBody Map<String, Object> body) {
String userId = str(body.getOrDefault("updatedBy", "system"));
body.put("updatedBy", userId);
Map<String, Object> updated = multilangService.updateLanguage(langCode, body);
return ResponseEntity.ok(ApiResponse.success(updated, "언어가 성공적으로 수정되었습니다."));
}
@DeleteMapping("/languages/{langCode}")
public ResponseEntity<ApiResponse<String>> deleteLanguage(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String langCode) {
multilangService.deleteLanguage(langCode);
return ResponseEntity.ok(ApiResponse.success("삭제 완료", "언어가 성공적으로 삭제되었습니다."));
}
@PutMapping("/languages/{langCode}/toggle")
public ResponseEntity<ApiResponse<String>> toggleLanguage(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String langCode) {
String result = multilangService.toggleLanguage(langCode);
return ResponseEntity.ok(ApiResponse.success(result, "언어가 " + result + "되었습니다."));
}
// ─────────────────────────────────────────────────────────────────────────
// 다국어 키 관리
// ─────────────────────────────────────────────────────────────────────────
@GetMapping("/keys")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getLangKeys(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
List<Map<String, Object>> keys = multilangService.getLangKeys(new HashMap<>(params));
return ResponseEntity.ok(ApiResponse.success(keys, "다국어 키 목록 조회 성공"));
}
@GetMapping("/keys/{keyId}/texts")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getLangTexts(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int keyId) {
return ResponseEntity.ok(ApiResponse.success(
multilangService.getLangTexts(keyId), "다국어 텍스트 조회 성공"));
}
@PostMapping("/keys")
public ResponseEntity<ApiResponse<Integer>> createLangKey(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
if (str(body.get("companyCode")).isEmpty() || str(body.get("langKey")).isEmpty()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("회사 코드와 언어 키는 필수입니다."));
}
String userId = str(body.getOrDefault("createdBy", "system"));
body.put("createdBy", userId);
body.put("updatedBy", userId);
int keyId = multilangService.createLangKey(body);
return ResponseEntity.status(201)
.body(ApiResponse.success(keyId, "다국어 키가 성공적으로 생성되었습니다."));
}
@PutMapping("/keys/{keyId}")
public ResponseEntity<ApiResponse<String>> updateLangKey(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int keyId,
@RequestBody Map<String, Object> body) {
String userId = str(body.getOrDefault("updatedBy", "system"));
body.put("updatedBy", userId);
multilangService.updateLangKey(keyId, body);
return ResponseEntity.ok(ApiResponse.success("수정 완료", "다국어 키가 성공적으로 수정되었습니다."));
}
@DeleteMapping("/keys/{keyId}")
public ResponseEntity<ApiResponse<String>> deleteLangKey(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int keyId) {
multilangService.deleteLangKey(keyId);
return ResponseEntity.ok(ApiResponse.success("삭제 완료", "다국어 키가 성공적으로 삭제되었습니다."));
}
@PutMapping("/keys/{keyId}/toggle")
public ResponseEntity<ApiResponse<String>> toggleLangKey(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int keyId) {
String result = multilangService.toggleLangKey(keyId);
return ResponseEntity.ok(ApiResponse.success(result, "다국어 키가 " + result + "되었습니다."));
}
// ─────────────────────────────────────────────────────────────────────────
// 다국어 텍스트 관리
// ─────────────────────────────────────────────────────────────────────────
@PostMapping("/keys/{keyId}/texts")
public ResponseEntity<ApiResponse<String>> saveLangTexts(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int keyId,
@RequestBody Map<String, Object> body) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> texts = (List<Map<String, Object>>) body.get("texts");
if (texts == null || texts.isEmpty()) {
return ResponseEntity.badRequest().body(ApiResponse.error("텍스트 데이터는 필수입니다."));
}
String userId = str(body.getOrDefault("updatedBy", "system"));
for (Map<String, Object> t : texts) {
if (t.get("createdBy") == null) t.put("createdBy", userId);
if (t.get("updatedBy") == null) t.put("updatedBy", userId);
}
multilangService.saveLangTexts(keyId, texts);
return ResponseEntity.ok(ApiResponse.success("저장 완료", "다국어 텍스트가 성공적으로 저장되었습니다."));
}
@GetMapping("/user-text/{companyCode}/{menuCode}/{langKey}")
public ResponseEntity<ApiResponse<String>> getUserText(
@PathVariable String companyCode,
@PathVariable String menuCode,
@PathVariable String langKey,
@RequestParam(required = false) String userLang) {
if (userLang == null || userLang.isEmpty()) {
return ResponseEntity.badRequest().body(ApiResponse.error("사용자 언어는 필수입니다."));
}
String text = multilangService.getUserText(companyCode, menuCode, langKey, userLang);
return ResponseEntity.ok(ApiResponse.success(text, "사용자별 다국어 텍스트 조회 성공"));
}
@GetMapping("/text/{companyCode}/{langKey}/{langCode}")
public ResponseEntity<ApiResponse<String>> getLangText(
@PathVariable String companyCode,
@PathVariable String langKey,
@PathVariable String langCode) {
String text = multilangService.getLangText(companyCode, langKey, langCode);
return ResponseEntity.ok(ApiResponse.success(text, "특정 키의 다국어 텍스트 조회 성공"));
}
// ─────────────────────────────────────────────────────────────────────────
// 카테고리 관리
// ─────────────────────────────────────────────────────────────────────────
@GetMapping("/categories")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCategories(
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
multilangService.getCategories(), "카테고리 목록 조회 성공"));
}
@GetMapping("/categories/{categoryId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCategoryById(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int categoryId) {
Map<String, Object> category = multilangService.getCategoryById(categoryId);
if (category == null) {
return ResponseEntity.status(404).body(ApiResponse.error("카테고리를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(category, "카테고리 상세 조회 성공"));
}
@GetMapping("/categories/{categoryId}/path")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCategoryPath(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int categoryId) {
return ResponseEntity.ok(ApiResponse.success(
multilangService.getCategoryPath(categoryId), "카테고리 경로 조회 성공"));
}
// ─────────────────────────────────────────────────────────────────────────
// 자동 생성 및 오버라이드
// ─────────────────────────────────────────────────────────────────────────
@PostMapping("/keys/generate")
public ResponseEntity<ApiResponse<Integer>> generateKey(
@RequestAttribute("companyCode") String requestCompanyCode,
@RequestBody Map<String, Object> body) {
String targetCompanyCode = str(body.get("companyCode"));
if (targetCompanyCode.isEmpty() || str(body.get("categoryId")).isEmpty()
|| str(body.get("keyMeaning")).isEmpty()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("회사 코드, 카테고리 ID, 키 의미는 필수입니다."));
}
if ("*".equals(targetCompanyCode) && !"*".equals(requestCompanyCode)) {
return ResponseEntity.status(403)
.body(ApiResponse.error("공통 키는 최고 관리자만 생성할 수 있습니다."));
}
if (!"*".equals(targetCompanyCode) && !"*".equals(requestCompanyCode)
&& !targetCompanyCode.equals(requestCompanyCode)) {
return ResponseEntity.status(403)
.body(ApiResponse.error("다른 회사의 키를 생성할 권한이 없습니다."));
}
body.put("createdBy", body.getOrDefault("createdBy", "system"));
int keyId = multilangService.generateKey(body);
return ResponseEntity.status(201)
.body(ApiResponse.success(keyId, "키가 성공적으로 생성되었습니다."));
}
@PostMapping("/keys/preview")
public ResponseEntity<ApiResponse<Map<String, Object>>> previewKey(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
Object categoryIdObj = body.get("categoryId");
String keyMeaning = str(body.get("keyMeaning"));
String targetCompanyCode = str(body.get("companyCode"));
if (categoryIdObj == null || keyMeaning.isEmpty() || targetCompanyCode.isEmpty()) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("카테고리 ID, 키 의미, 회사 코드는 필수입니다."));
}
int categoryId = toInt(categoryIdObj);
Map<String, Object> preview = multilangService.previewKey(categoryId, keyMeaning, targetCompanyCode);
return ResponseEntity.ok(ApiResponse.success(preview, "키 미리보기 성공"));
}
@PostMapping("/keys/override")
public ResponseEntity<ApiResponse<Integer>> createOverrideKey(
@RequestAttribute("companyCode") String requestCompanyCode,
@RequestBody Map<String, Object> body) {
String targetCompanyCode = str(body.get("companyCode"));
if (targetCompanyCode.isEmpty() || body.get("baseKeyId") == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("회사 코드와 원본 키 ID는 필수입니다."));
}
if ("*".equals(targetCompanyCode)) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("공통 키에 대한 오버라이드는 생성할 수 없습니다."));
}
if (!"*".equals(requestCompanyCode) && !targetCompanyCode.equals(requestCompanyCode)) {
return ResponseEntity.status(403)
.body(ApiResponse.error("다른 회사의 오버라이드 키를 생성할 권한이 없습니다."));
}
body.put("createdBy", body.getOrDefault("createdBy", "system"));
int keyId = multilangService.createOverrideKey(body);
return ResponseEntity.status(201)
.body(ApiResponse.success(keyId, "오버라이드 키가 성공적으로 생성되었습니다."));
}
@GetMapping("/keys/overrides/{companyCode}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getOverrideKeys(
@RequestAttribute("companyCode") String requestCompanyCode,
@PathVariable String companyCode) {
if (!"*".equals(requestCompanyCode) && !companyCode.equals(requestCompanyCode)) {
return ResponseEntity.status(403)
.body(ApiResponse.error("다른 회사의 오버라이드 키를 조회할 권한이 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(
multilangService.getOverrideKeys(companyCode), "오버라이드 키 목록 조회 성공"));
}
// ─────────────────────────────────────────────────────────────────────────
// 화면 라벨 다국어 자동 생성
// ─────────────────────────────────────────────────────────────────────────
@PostMapping("/screen-labels")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> generateScreenLabelKeys(
@RequestAttribute("companyCode") String requestCompanyCode,
@RequestBody Map<String, Object> body) {
Object screenIdObj = body.get("screenId");
@SuppressWarnings("unchecked")
List<Map<String, Object>> labels = (List<Map<String, Object>>) body.get("labels");
if (screenIdObj == null) {
return ResponseEntity.badRequest().body(ApiResponse.error("screenId는 필수입니다."));
}
if (labels == null || labels.isEmpty()) {
return ResponseEntity.badRequest().body(ApiResponse.error("labels 배열이 필요합니다."));
}
// companyCode, companyName이 body에 없으면 requestCompanyCode 사용
if (!body.containsKey("companyCode") || str(body.get("companyCode")).isEmpty()) {
body.put("companyCode", requestCompanyCode);
}
if (!body.containsKey("companyName") || str(body.get("companyName")).isEmpty()) {
body.put("companyName", requestCompanyCode);
}
body.put("screenId", toInt(screenIdObj));
List<Map<String, Object>> results = multilangService.generateScreenLabelKeys(body);
return ResponseEntity.ok(ApiResponse.success(
results, results.size() + "개의 다국어 키가 생성되었습니다."));
}
// ─────────────────────────────────────────────────────────────────────────
// Private utilities
// ─────────────────────────────────────────────────────────────────────────
private String str(Object o) {
return o == null ? "" : o.toString();
}
private int toInt(Object o) {
if (o == null) return 0;
if (o instanceof Number) return ((Number) o).intValue();
try { return Integer.parseInt(o.toString()); } catch (NumberFormatException e) { return 0; }
}
}
@@ -0,0 +1,100 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.NodeExternalConnectionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* Node 플로우 전용 외부 DB 연결 컨트롤러
*
* Node.js dataflow/node-external-connections.ts 포팅.
*
* GET /api/dataflow/node-external-connections/tested — 테스트 성공 커넥션 목록
* GET /api/dataflow/node-external-connections/{id}/tables — 외부 DB 테이블 목록
* GET /api/dataflow/node-external-connections/{id}/tables/{tableName}/columns — 외부 DB 컬럼 목록
*/
@RestController
@RequestMapping("/api/dataflow/node-external-connections")
@RequiredArgsConstructor
@Slf4j
public class NodeExternalConnectionController {
private final NodeExternalConnectionService service;
// ─────────────────────────────────────────────────────────
// GET /tested
// Node.js: router.get("/tested", ...)
// ─────────────────────────────────────────────────────────
@GetMapping("/tested")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTestedConnections(
@RequestAttribute("companyCode") String companyCode) {
try {
log.info("노드 플로우용 테스트 완료된 커넥션 조회 요청 - companyCode: {}", companyCode);
List<Map<String, Object>> valid = service.getTestedConnections(companyCode);
return ResponseEntity.ok(ApiResponse.success(valid,
"테스트에 성공한 " + valid.size() + "개의 커넥션을 조회했습니다."));
} catch (Exception e) {
log.error("노드 플로우용 커넥션 조회 오류", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("서버 내부 오류가 발생했습니다."));
}
}
// ─────────────────────────────────────────────────────────
// GET /{id}/tables
// Node.js: router.get("/:id/tables", ...)
// ─────────────────────────────────────────────────────────
@GetMapping("/{id}/tables")
public ResponseEntity<?> getTables(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id) {
try {
log.info("외부 DB 테이블 목록 조회: connectionId={}", id);
Map<String, Object> result = service.getTablesFromConnection(id);
if (Boolean.TRUE.equals(result.get("success"))) {
return ResponseEntity.ok(result);
}
return ResponseEntity.status(400).body(result);
} catch (Exception e) {
log.error("외부 DB 테이블 목록 조회 오류: id={}", id, e);
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "서버 내부 오류가 발생했습니다.",
"error", e.getMessage()));
}
}
// ─────────────────────────────────────────────────────────
// GET /{id}/tables/{tableName}/columns
// Node.js: router.get("/:id/tables/:tableName/columns", ...)
// ─────────────────────────────────────────────────────────
@GetMapping("/{id}/tables/{tableName}/columns")
public ResponseEntity<?> getTableColumns(
@RequestAttribute("companyCode") String companyCode,
@PathVariable int id,
@PathVariable String tableName) {
try {
log.info("외부 DB 컬럼 목록 조회: connectionId={}, table={}", id, tableName);
Map<String, Object> result = service.getColumnsFromConnection(id, tableName);
if (Boolean.TRUE.equals(result.get("success"))) {
return ResponseEntity.ok(result);
}
return ResponseEntity.status(400).body(result);
} catch (Exception e) {
log.error("외부 DB 컬럼 목록 조회 오류: id={}, table={}", id, tableName, e);
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "서버 내부 오류가 발생했습니다.",
"error", e.getMessage()));
}
}
}
@@ -0,0 +1,180 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.NodeFlowService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 노드 플로우 컨트롤러 (/api/dataflow/node-flows)
*
* Node.js node-flows.ts 포팅.
*/
@RestController
@RequestMapping("/api/dataflow/node-flows")
@RequiredArgsConstructor
@Slf4j
public class NodeFlowController {
private final NodeFlowService service;
// ──────────────────────────────────────────────────────────────
// 목록 조회
// ──────────────────────────────────────────────────────────────
/** GET / */
@GetMapping
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowList(
@RequestAttribute("companyCode") String companyCode) {
try {
return ResponseEntity.ok(ApiResponse.success(service.getFlowList(companyCode)));
} catch (Exception e) {
log.error("플로우 목록 조회 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("플로우 목록 조회 중 오류가 발생했습니다."));
}
}
// ──────────────────────────────────────────────────────────────
// 상세 조회
// ──────────────────────────────────────────────────────────────
/** GET /{flowId} */
@GetMapping("/{flowId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getFlowById(
@PathVariable int flowId,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> flow = service.getFlowById(flowId, companyCode);
if (flow == null) {
return ResponseEntity.status(404).body(ApiResponse.error("플로우를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(flow));
} catch (Exception e) {
log.error("플로우 상세 조회 오류: flowId={}", flowId, e);
return ResponseEntity.status(500).body(ApiResponse.error("플로우 조회 중 오류가 발생했습니다."));
}
}
// ──────────────────────────────────────────────────────────────
// 생성
// ──────────────────────────────────────────────────────────────
/** POST / */
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createFlow(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestAttribute("userName") String userName,
@RequestBody Map<String, Object> body) {
try {
body.put("companyCode", companyCode);
body.put("userId", userId);
body.put("userName", userName);
return ResponseEntity.ok(ApiResponse.success(service.createFlow(body)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("플로우 생성 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("플로우 생성 중 오류가 발생했습니다."));
}
}
// ──────────────────────────────────────────────────────────────
// 수정
// ──────────────────────────────────────────────────────────────
/** PUT / (flowId는 body에 포함) */
@PutMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> updateFlow(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestAttribute("userName") String userName,
@RequestBody Map<String, Object> body) {
try {
body.put("companyCode", companyCode);
body.put("userId", userId);
body.put("userName", userName);
return ResponseEntity.ok(ApiResponse.success(service.updateFlow(body)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("플로우 수정 오류", e);
return ResponseEntity.status(500).body(ApiResponse.error("플로우 수정 중 오류가 발생했습니다."));
}
}
// ──────────────────────────────────────────────────────────────
// 삭제
// ──────────────────────────────────────────────────────────────
/** DELETE /{flowId} */
@DeleteMapping("/{flowId}")
public ResponseEntity<ApiResponse<Void>> deleteFlow(
@PathVariable int flowId,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestAttribute("userName") String userName) {
try {
service.deleteFlow(flowId, companyCode, userId, userName);
return ResponseEntity.ok(ApiResponse.success(null, "플로우가 삭제되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("플로우 삭제 오류: flowId={}", flowId, e);
return ResponseEntity.status(500).body(ApiResponse.error("플로우 삭제 중 오류가 발생했습니다."));
}
}
// ──────────────────────────────────────────────────────────────
// 소스 테이블 추출
// ──────────────────────────────────────────────────────────────
/** GET /{flowId}/source-table */
@GetMapping("/{flowId}/source-table")
public ResponseEntity<ApiResponse<Map<String, Object>>> getSourceTable(
@PathVariable int flowId) {
try {
Map<String, Object> result = service.getSourceTable(flowId);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("소스 테이블을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("소스 테이블 추출 오류: flowId={}", flowId, e);
return ResponseEntity.status(500).body(ApiResponse.error("소스 테이블 추출 중 오류가 발생했습니다."));
}
}
// ──────────────────────────────────────────────────────────────
// 플로우 실행
// ──────────────────────────────────────────────────────────────
/** POST /{flowId}/execute */
@PostMapping("/{flowId}/execute")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeFlow(
@PathVariable int flowId,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestAttribute("userName") String userName,
@RequestBody(required = false) Map<String, Object> body) {
try {
Map<String, Object> contextData = body != null ? body : new java.util.LinkedHashMap<>();
contextData.put("companyCode", companyCode);
contextData.put("userId", userId);
contextData.put("userName", userName);
Map<String, Object> result = service.executeFlow(flowId, contextData);
return ResponseEntity.ok(ApiResponse.success(result));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("플로우 실행 오류: flowId={}", flowId, e);
return ResponseEntity.status(500).body(ApiResponse.error("플로우 실행 중 오류가 발생했습니다."));
}
}
}
@@ -0,0 +1,258 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.NumberingRuleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/numbering-rule")
@RequiredArgsConstructor
@Slf4j
public class NumberingRuleController {
private final NumberingRuleService numberingRuleService;
// ================================================================
// ■ 규칙 목록 / 단건 조회
// ================================================================
/** GET / → 회사별 채번 규칙 전체 목록 */
@GetMapping
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getRuleList(
@RequestAttribute("companyCode") String companyCode) {
List<Map<String, Object>> list = numberingRuleService.getRuleList(companyCode);
return ResponseEntity.ok(ApiResponse.success(list, "채번 규칙 목록을 조회했습니다."));
}
/** GET /available → 메뉴 기반 사용 가능 규칙 (menuObjid 없음) */
@GetMapping("/available")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAvailableRulesForMenu(
@RequestAttribute("companyCode") String companyCode) {
List<Map<String, Object>> list = numberingRuleService.getAvailableRulesForMenu(companyCode, null);
return ResponseEntity.ok(ApiResponse.success(list, "사용 가능한 채번 규칙을 조회했습니다."));
}
/** GET /available/{menuObjid} → 메뉴 기반 사용 가능 규칙 */
@GetMapping("/available/{menuObjid}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAvailableRulesForMenuWithId(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer menuObjid) {
List<Map<String, Object>> list = numberingRuleService.getAvailableRulesForMenu(companyCode, menuObjid);
return ResponseEntity.ok(ApiResponse.success(list, "사용 가능한 채번 규칙을 조회했습니다."));
}
/** GET /available-for-screen?tableName= → 화면 기반 사용 가능 규칙 */
@GetMapping("/available-for-screen")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAvailableRulesForScreen(
@RequestAttribute("companyCode") String companyCode,
@RequestParam(required = false) String tableName) {
List<Map<String, Object>> list = numberingRuleService.getAvailableRulesForScreen(companyCode, tableName);
return ResponseEntity.ok(ApiResponse.success(list, "화면별 채번 규칙을 조회했습니다."));
}
/** GET /by-column/{tableName}/{columnName} → 테이블+컬럼 기반 규칙 조회 */
@GetMapping("/by-column/{tableName}/{columnName}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getNumberingRuleByColumn(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@PathVariable String columnName) {
Map<String, Object> rule = numberingRuleService.getNumberingRuleByColumn(
companyCode, tableName, columnName);
if (rule == null) {
return ResponseEntity.ok(ApiResponse.success(null, "채번 규칙을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(rule, "채번 규칙을 조회했습니다."));
}
// ================================================================
// ■ 테스트 테이블 Operations
// ================================================================
/** GET /test/list → 테스트용 규칙 목록 */
@GetMapping("/test/list")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getRulesFromTest(
@RequestAttribute("companyCode") String companyCode) {
List<Map<String, Object>> list = numberingRuleService.getRulesFromTest(companyCode, null);
return ResponseEntity.ok(ApiResponse.success(list, "테스트 채번 규칙 목록을 조회했습니다."));
}
/** GET /test/list/{menuObjid} → 테스트용 규칙 목록 (메뉴 필터) */
@GetMapping("/test/list/{menuObjid}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getRulesFromTestWithMenu(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer menuObjid) {
List<Map<String, Object>> list = numberingRuleService.getRulesFromTest(companyCode, menuObjid);
return ResponseEntity.ok(ApiResponse.success(list, "테스트 채번 규칙 목록을 조회했습니다."));
}
/** GET /test/by-column/{tableName}/{columnName} → 테스트 테이블+컬럼 기반 조회 */
@GetMapping("/test/by-column/{tableName}/{columnName}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTestRuleByColumn(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@PathVariable String columnName) {
Map<String, Object> rule = numberingRuleService.getNumberingRuleByColumn(
companyCode, tableName, columnName);
if (rule == null) {
return ResponseEntity.ok(ApiResponse.success(null, "채번 규칙을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(rule, "채번 규칙을 조회했습니다."));
}
/** POST /test/save → UPSERT */
@PostMapping("/test/save")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveRuleToTest(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
if (body.get("ruleId") == null || body.get("ruleName") == null) {
return ResponseEntity.badRequest().body(ApiResponse.error("ruleId와 ruleName이 필요합니다."));
}
Map<String, Object> result = numberingRuleService.saveRuleToTest(body, companyCode);
return ResponseEntity.ok(ApiResponse.success(result, "테스트 채번 규칙이 저장되었습니다."));
}
/** DELETE /test/{ruleId} → 테스트 규칙 삭제 */
@DeleteMapping("/test/{ruleId}")
public ResponseEntity<ApiResponse<Void>> deleteRuleFromTest(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String ruleId) {
numberingRuleService.deleteRuleFromTest(ruleId, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "테스트 채번 규칙이 삭제되었습니다."));
}
/** POST /test/{ruleId}/preview → 테스트 미리보기 */
@PostMapping("/test/{ruleId}/preview")
public ResponseEntity<ApiResponse<Map<String, Object>>> previewCodeFromTest(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String ruleId,
@RequestBody(required = false) Map<String, Object> body) {
@SuppressWarnings("unchecked")
Map<String, Object> formData = body != null ? (Map<String, Object>) body.get("formData") : null;
String manualInputValue = body != null ? (String) body.get("manualInputValue") : null;
String code = numberingRuleService.previewCode(ruleId, companyCode, formData, manualInputValue);
return ResponseEntity.ok(ApiResponse.success(Map.of("code", code), "미리보기 생성이 완료되었습니다."));
}
// ================================================================
// ■ 단건 CRUD
// ================================================================
/** GET /{ruleId} → 단건 조회 */
@GetMapping("/{ruleId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getRuleById(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String ruleId) {
Map<String, Object> rule = numberingRuleService.getRuleById(ruleId, companyCode);
if (rule == null) {
return ResponseEntity.ok(ApiResponse.success(null, "채번 규칙을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(rule, "채번 규칙을 조회했습니다."));
}
/** POST / → 규칙 생성 */
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createRule(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
if (body.get("ruleId") == null || body.get("ruleName") == null || body.get("parts") == null) {
return ResponseEntity.badRequest().body(ApiResponse.error("ruleId, ruleName, parts가 필요합니다."));
}
if (userId != null) body.put("userId", userId);
Map<String, Object> result = numberingRuleService.createRule(body, companyCode);
return ResponseEntity.status(201).body(ApiResponse.success(result, "채번 규칙이 생성되었습니다."));
}
/** PUT /{ruleId} → 규칙 수정 */
@PutMapping("/{ruleId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateRule(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String ruleId,
@RequestBody Map<String, Object> body) {
Map<String, Object> result = numberingRuleService.updateRule(ruleId, body, companyCode);
return ResponseEntity.ok(ApiResponse.success(result, "채번 규칙이 수정되었습니다."));
}
/** DELETE /{ruleId} → 규칙 삭제 */
@DeleteMapping("/{ruleId}")
public ResponseEntity<ApiResponse<Void>> deleteRule(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String ruleId) {
numberingRuleService.deleteRule(ruleId, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "채번 규칙이 삭제되었습니다."));
}
// ================================================================
// ■ 코드 생성 Actions
// ================================================================
/** POST /{ruleId}/preview → 미리보기 (순번 증가 없음) */
@PostMapping("/{ruleId}/preview")
public ResponseEntity<ApiResponse<Map<String, Object>>> previewCode(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String ruleId,
@RequestBody(required = false) Map<String, Object> body) {
@SuppressWarnings("unchecked")
Map<String, Object> formData = body != null ? (Map<String, Object>) body.get("formData") : null;
String manualInputValue = body != null ? (String) body.get("manualInputValue") : null;
String code = numberingRuleService.previewCode(ruleId, companyCode, formData, manualInputValue);
return ResponseEntity.ok(ApiResponse.success(Map.of("code", code), "미리보기 생성이 완료되었습니다."));
}
/** POST /{ruleId}/allocate → 코드 할당 (순번 증가) */
@PostMapping("/{ruleId}/allocate")
public ResponseEntity<ApiResponse<Map<String, Object>>> allocateCode(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String ruleId,
@RequestBody(required = false) Map<String, Object> body) {
@SuppressWarnings("unchecked")
Map<String, Object> formData = body != null ? (Map<String, Object>) body.get("formData") : null;
String userInputCode = body != null ? (String) body.get("userInputCode") : null;
String code = numberingRuleService.allocateCode(ruleId, companyCode, formData, userInputCode);
return ResponseEntity.ok(ApiResponse.success(Map.of("code", code), "코드 할당이 완료되었습니다."));
}
/** POST /{ruleId}/generate (deprecated) → allocateCode 위임 */
@PostMapping("/{ruleId}/generate")
public ResponseEntity<ApiResponse<Map<String, Object>>> generateCode(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String ruleId) {
String code = numberingRuleService.generateCode(ruleId, companyCode);
return ResponseEntity.ok(ApiResponse.success(Map.of("code", code), "코드 생성이 완료되었습니다."));
}
/** POST /{ruleId}/reset → 순번 초기화 */
@PostMapping("/{ruleId}/reset")
public ResponseEntity<ApiResponse<Void>> resetSequence(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String ruleId) {
numberingRuleService.resetSequence(ruleId, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "시퀀스가 초기화되었습니다."));
}
// ================================================================
// ■ Admin
// ================================================================
/** POST /copy-for-company → 회사간 규칙 복제 (SUPER_ADMIN) */
@PostMapping("/copy-for-company")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyRulesForCompany(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
String sourceCompanyCode = (String) body.get("sourceCompanyCode");
String targetCompanyCode = (String) body.get("targetCompanyCode");
if (sourceCompanyCode == null || targetCompanyCode == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("sourceCompanyCode와 targetCompanyCode가 필요합니다."));
}
Map<String, Object> result = numberingRuleService.copyRulesForCompany(
sourceCompanyCode, targetCompanyCode);
return ResponseEntity.ok(ApiResponse.success(result, "채번 규칙 복제가 완료되었습니다."));
}
}
@@ -0,0 +1,62 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.OpenApiProxyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/open-api-proxy")
@RequiredArgsConstructor
@Slf4j
public class OpenApiProxyController {
private final OpenApiProxyService openApiProxyService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getOpenApiProxyList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(openApiProxyService.getOpenApiProxyList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getOpenApiProxyInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(openApiProxyService.getOpenApiProxyInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Integer>> insertOpenApiProxy(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(openApiProxyService.insertOpenApiProxy(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Integer>> updateOpenApiProxy(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(openApiProxyService.updateOpenApiProxy(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Integer>> deleteOpenApiProxy(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(openApiProxyService.deleteOpenApiProxy(params)));
}
}
@@ -0,0 +1,66 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.PackagingService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/packaging")
@RequiredArgsConstructor
@Slf4j
public class PackagingController {
private final PackagingService packagingService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getPackagingList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(packagingService.getPackagingList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getPackagingInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(packagingService.getPackagingInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertPackaging(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(packagingService.insertPackaging(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updatePackaging(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(packagingService.updatePackaging(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deletePackaging(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(packagingService.deletePackaging(params)));
}
}
@@ -0,0 +1,40 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.PopActionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/pop-action")
@RequiredArgsConstructor
@Slf4j
public class PopActionController {
private final PopActionService popActionService;
/**
* POST /api/pop-action/execute-action
* Node.js popActionRoutes.ts POST /execute-action 대응
*/
@PostMapping("/execute-action")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeAction(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> result = popActionService.executeAction(body, companyCode, userId);
String message = result.containsKey("message") ? result.get("message").toString()
: result.get("processedCount") + "건 처리 완료";
return ResponseEntity.ok(ApiResponse.success(result, message));
} catch (PopActionService.PreConditionFailException e) {
return ResponseEntity.status(409).body(ApiResponse.error(e.getMessage()));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
}
}
@@ -0,0 +1,62 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.PopProductionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/pop-production")
@RequiredArgsConstructor
@Slf4j
public class PopProductionController {
private final PopProductionService popProductionService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getPopProductionList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(popProductionService.getPopProductionList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getPopProductionInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(popProductionService.getPopProductionInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertPopProduction(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(popProductionService.insertPopProduction(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updatePopProduction(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(popProductionService.updatePopProduction(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deletePopProduction(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(popProductionService.deletePopProduction(params)));
}
}
@@ -0,0 +1,176 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ProcessWorkStandardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/process-work-standard")
@RequiredArgsConstructor
@Slf4j
public class ProcessWorkStandardController {
private final ProcessWorkStandardService processWorkStandardService;
@GetMapping("/items")
public ResponseEntity<ApiResponse<Map<String, Object>>> getProcessWorkStandardItemList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.getProcessWorkStandardItemList(params)));
}
@GetMapping("/items/{itemCode}/routings")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getProcessWorkStandardRoutingList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String itemCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("itemCode", itemCode);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.getProcessWorkStandardRoutingList(params)));
}
@PutMapping("/versions/{versionId}/set-default")
public ResponseEntity<ApiResponse<Map<String, Object>>> setProcessWorkStandardDefaultVersion(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long versionId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("versionId", versionId);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.setProcessWorkStandardDefaultVersion(params)));
}
@PutMapping("/versions/{versionId}/unset-default")
public ResponseEntity<ApiResponse<Map<String, Object>>> unsetProcessWorkStandardDefaultVersion(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long versionId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("versionId", versionId);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.unsetProcessWorkStandardDefaultVersion(params)));
}
@GetMapping("/routing-detail/{routingDetailId}/work-items")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getProcessWorkStandardWorkItemList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long routingDetailId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("routingDetailId", routingDetailId);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.getProcessWorkStandardWorkItemList(params)));
}
@PostMapping("/work-items")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertProcessWorkStandardWorkItem(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.insertProcessWorkStandardWorkItem(body)));
}
@PutMapping("/work-items/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateProcessWorkStandardWorkItem(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.updateProcessWorkStandardWorkItem(body)));
}
@DeleteMapping("/work-items/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteProcessWorkStandardWorkItem(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.deleteProcessWorkStandardWorkItem(params)));
}
@GetMapping("/work-items/{workItemId}/details")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getProcessWorkStandardWorkItemDetailList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long workItemId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("workItemId", workItemId);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.getProcessWorkStandardWorkItemDetailList(params)));
}
@PostMapping("/work-item-details")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertProcessWorkStandardWorkItemDetail(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.insertProcessWorkStandardWorkItemDetail(body)));
}
@PutMapping("/work-item-details/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateProcessWorkStandardWorkItemDetail(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.updateProcessWorkStandardWorkItemDetail(body)));
}
@DeleteMapping("/work-item-details/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteProcessWorkStandardWorkItemDetail(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.deleteProcessWorkStandardWorkItemDetail(params)));
}
@PutMapping("/save-all")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveAllProcessWorkStandard(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.saveAllProcessWorkStandard(body)));
}
@GetMapping("/registered-items/{screenCode}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getProcessWorkStandardRegisteredItemList(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String screenCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("screenCode", screenCode);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.getProcessWorkStandardRegisteredItemList(params)));
}
@PostMapping("/registered-items")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertProcessWorkStandardRegisteredItem(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.insertProcessWorkStandardRegisteredItem(body)));
}
@PostMapping("/registered-items/batch")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertProcessWorkStandardRegisteredItemBatch(
@RequestAttribute("companyCode") String companyCode,
@RequestBody List<Map<String, Object>> body) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("items", body);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.insertProcessWorkStandardRegisteredItemBatch(params)));
}
@DeleteMapping("/registered-items/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteProcessWorkStandardRegisteredItem(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(processWorkStandardService.deleteProcessWorkStandardRegisteredItem(params)));
}
}
@@ -0,0 +1,74 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ProductionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/production")
@RequiredArgsConstructor
@Slf4j
public class ProductionController {
private final ProductionService productionService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getProductionList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(productionService.getProductionList(params)));
}
@GetMapping("/plans")
public ResponseEntity<ApiResponse<Map<String, Object>>> getProductionPlans(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(productionService.getProductionList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getProductionInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(productionService.getProductionInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertProduction(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(productionService.insertProduction(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateProduction(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(productionService.updateProduction(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteProduction(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(productionService.deleteProduction(params)));
}
}
@@ -0,0 +1,220 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ReportService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/report")
@RequiredArgsConstructor
@Slf4j
public class ReportController {
private final ReportService reportService;
// ── 외부 DB 연결 목록 ─────────────────────────────────────────────────────
@GetMapping("/external-connections")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getExternalConnections(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(reportService.getExternalConnections(params)));
}
// ── 템플릿 ────────────────────────────────────────────────────────────────
@GetMapping("/templates")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTemplates() {
return ResponseEntity.ok(ApiResponse.success(reportService.getTemplates()));
}
@PostMapping("/templates")
public ResponseEntity<ApiResponse<Map<String, Object>>> createTemplate(
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
body.put("createdBy", userId);
Map<String, Object> result = reportService.createTemplate(body);
return ResponseEntity.status(201).body(ApiResponse.success(result));
}
@PostMapping("/templates/create-from-layout")
public ResponseEntity<ApiResponse<Map<String, Object>>> createTemplateFromLayout(
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
body.put("createdBy", userId);
Map<String, Object> result = reportService.createTemplateFromLayout(body);
return ResponseEntity.status(201).body(ApiResponse.success(result));
}
@DeleteMapping("/templates/{templateId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteTemplate(
@PathVariable String templateId) {
Map<String, Object> params = new HashMap<>();
params.put("templateId", templateId);
return ResponseEntity.ok(ApiResponse.success(reportService.deleteTemplate(params)));
}
// ── 이미지 업로드 (stub) ──────────────────────────────────────────────────
@PostMapping("/upload-image")
public ResponseEntity<ApiResponse<Map<String, Object>>> uploadImage() {
return ResponseEntity.status(501).body(
ApiResponse.error("이미지 업로드는 현재 지원되지 않습니다."));
}
// ── Word 내보내기 (stub) ──────────────────────────────────────────────────
@PostMapping("/export-word")
public ResponseEntity<ApiResponse<Map<String, Object>>> exportToWord() {
return ResponseEntity.status(501).body(
ApiResponse.error("Word 내보내기는 현재 지원되지 않습니다."));
}
// ── 리포트 목록 ───────────────────────────────────────────────────────────
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getReportList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(reportService.getReportList(params)));
}
// ── 리포트 생성 ───────────────────────────────────────────────────────────
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createReport(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestBody Map<String, Object> body) {
if (body.get("reportNameKor") == null || body.get("reportType") == null) {
return ResponseEntity.status(400).body(
ApiResponse.error("리포트명과 리포트 타입은 필수입니다."));
}
body.put("companyCode", companyCode);
body.put("createdBy", userId);
Map<String, Object> result = reportService.createReport(body);
return ResponseEntity.status(201).body(ApiResponse.success(result, "리포트가 생성되었습니다."));
}
// ── 리포트 복사 ───────────────────────────────────────────────────────────
@PostMapping("/{reportId}/copy")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyReport(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String reportId) {
Map<String, Object> params = new HashMap<>();
params.put("reportId", reportId);
params.put("companyCode", companyCode);
params.put("userId", userId);
Map<String, Object> result = reportService.copyReport(params);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("리포트를 찾을 수 없습니다."));
}
return ResponseEntity.status(201).body(ApiResponse.success(result));
}
// ── 템플릿으로 저장 ───────────────────────────────────────────────────────
@PostMapping("/{reportId}/save-as-template")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveAsTemplate(
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String reportId,
@RequestBody Map<String, Object> body) {
body.put("reportId", reportId);
body.put("userId", userId);
Map<String, Object> result = reportService.saveAsTemplate(body);
return ResponseEntity.status(201).body(ApiResponse.success(result));
}
// ── 레이아웃 조회/저장 ────────────────────────────────────────────────────
@GetMapping("/{reportId}/layout")
public ResponseEntity<ApiResponse<Map<String, Object>>> getLayout(
@PathVariable String reportId) {
Map<String, Object> params = Map.of("reportId", reportId);
Map<String, Object> result = reportService.getLayout(params);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("레이아웃을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@PutMapping("/{reportId}/layout")
public ResponseEntity<ApiResponse<Map<String, Object>>> saveLayout(
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String reportId,
@RequestBody Map<String, Object> body) {
body.put("reportId", reportId);
body.put("userId", userId);
Map<String, Object> result = reportService.saveLayout(body);
return ResponseEntity.ok(ApiResponse.success(result));
}
// ── 쿼리 실행 ────────────────────────────────────────────────────────────
@PostMapping("/{reportId}/queries/{queryId}/execute")
public ResponseEntity<ApiResponse<Map<String, Object>>> executeQuery(
@PathVariable String reportId,
@PathVariable String queryId,
@RequestBody Map<String, Object> body) {
body.put("reportId", reportId);
body.put("queryId", queryId);
Map<String, Object> result = reportService.executeQuery(body);
return ResponseEntity.ok(ApiResponse.success(result));
}
// ── 리포트 상세 / 수정 / 삭제 ────────────────────────────────────────────
@GetMapping("/{reportId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getReportInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String reportId) {
Map<String, Object> params = new HashMap<>();
params.put("reportId", reportId);
params.put("companyCode", companyCode);
Map<String, Object> result = reportService.getReportInfo(params);
if (result == null) {
return ResponseEntity.status(404).body(ApiResponse.error("리포트를 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@PutMapping("/{reportId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateReport(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute(value = "userId", required = false) String userId,
@PathVariable String reportId,
@RequestBody Map<String, Object> body) {
body.put("reportId", reportId);
body.put("companyCode", companyCode);
body.put("updatedBy", userId);
return ResponseEntity.ok(ApiResponse.success(reportService.updateReport(body)));
}
@DeleteMapping("/{reportId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteReport(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String reportId) {
Map<String, Object> params = new HashMap<>();
params.put("reportId", reportId);
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(reportService.deleteReport(params)));
}
// ── 이전 /list 경로 호환 ──────────────────────────────────────────────────
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getReportListLegacy(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(reportService.getReportList(params)));
}
}
@@ -0,0 +1,64 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.RiskAlertService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/risk-alert")
@RequiredArgsConstructor
@Slf4j
public class RiskAlertController {
private final RiskAlertService riskAlertService;
/** GET / → 전체 알림 목록 */
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getAllAlerts(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(riskAlertService.getAllAlerts(params)));
}
/** POST /refresh → 알림 새로고침 */
@PostMapping("/refresh")
public ResponseEntity<ApiResponse<Map<String, Object>>> refreshAlerts(
@RequestAttribute("companyCode") String companyCode,
@RequestBody(required = false) Map<String, Object> body) {
Map<String, Object> params = body != null ? new HashMap<>(body) : new HashMap<>();
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(riskAlertService.refreshAlerts(params)));
}
/** GET /weather → 기상 알림 목록 */
@GetMapping("/weather")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getWeatherAlerts(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(riskAlertService.getWeatherAlerts(params)));
}
/** GET /accidents → 사고 알림 목록 */
@GetMapping("/accidents")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAccidentAlerts(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(riskAlertService.getAccidentAlerts(params)));
}
/** GET /roadworks → 공사 알림 목록 */
@GetMapping("/roadworks")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getRoadworkAlerts(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(riskAlertService.getRoadworkAlerts(params)));
}
}
@@ -0,0 +1,436 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.RoleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/roles")
@RequiredArgsConstructor
@Slf4j
public class RoleController {
private final RoleService roleService;
// ──────────────────────────────────────────────────
// 권한 그룹 CRUD
// ──────────────────────────────────────────────────
/**
* GET /api/roles
* 권한 그룹 목록 조회 (관리자 전용)
*/
@GetMapping
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getRoleGroups(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestParam Map<String, Object> params) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
// SUPER_ADMIN: 요청 파라미터의 companyCode 사용 (없으면 전체)
// 그 외: 자신의 companyCode 강제 적용
if (!isSuperAdmin(role)) {
params.put("companyCode", companyCode);
}
return ResponseEntity.ok(ApiResponse.success(roleService.getRoleGroups(params), "권한 그룹 목록 조회 성공"));
}
/**
* GET /api/roles/{id}
* 권한 그룹 상세 조회
*/
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getRoleGroupById(
@PathVariable String id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("objid", parseLong(id));
Map<String, Object> group = roleService.getRoleGroupById(params);
if (group == null) {
return ResponseEntity.status(404).body(ApiResponse.error("권한 그룹을 찾을 수 없습니다."));
}
if (!isSuperAdmin(role) && !companyCode.equals(group.get("companyCode"))) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(group, "권한 그룹 상세 조회 성공"));
}
/**
* POST /api/roles
* 권한 그룹 생성
*/
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createRoleGroup(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
String targetCompany = (String) body.get("companyCode");
if (!isSuperAdmin(role) && !companyCode.equals(targetCompany)) {
return ResponseEntity.status(403).body(ApiResponse.error("다른 회사의 권한 그룹을 생성할 수 없습니다."));
}
Map<String, Object> params = new HashMap<>(body);
params.put("writer", userId);
Map<String, Object> created = roleService.createRoleGroup(params);
return ResponseEntity.status(201).body(ApiResponse.success(created, "권한 그룹 생성 성공"));
}
/**
* PUT /api/roles/{id}
* 권한 그룹 수정
*/
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateRoleGroup(
@PathVariable String id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestBody Map<String, Object> body) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
Map<String, Object> findParams = Map.of("objid", parseLong(id));
Map<String, Object> existing = roleService.getRoleGroupById(findParams);
if (existing == null) {
return ResponseEntity.status(404).body(ApiResponse.error("권한 그룹을 찾을 수 없습니다."));
}
if (!isSuperAdmin(role) && !companyCode.equals(existing.get("companyCode"))) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다."));
}
Map<String, Object> params = new HashMap<>(body);
params.put("objid", parseLong(id));
Map<String, Object> updated = roleService.updateRoleGroup(params);
return ResponseEntity.ok(ApiResponse.success(updated, "권한 그룹 수정 성공"));
}
/**
* DELETE /api/roles/{id}
* 권한 그룹 삭제
*/
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteRoleGroup(
@PathVariable String id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
Map<String, Object> findParams = Map.of("objid", parseLong(id));
Map<String, Object> existing = roleService.getRoleGroupById(findParams);
if (existing == null) {
return ResponseEntity.status(404).body(ApiResponse.error("권한 그룹을 찾을 수 없습니다."));
}
if (!isSuperAdmin(role) && !companyCode.equals(existing.get("companyCode"))) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("objid", parseLong(id));
roleService.deleteRoleGroup(params);
return ResponseEntity.ok(ApiResponse.success(null, "권한 그룹 삭제 성공"));
}
// ──────────────────────────────────────────────────
// 멤버 관리 — /{id}/members 보다 먼저 정의된 2단계 경로
// ──────────────────────────────────────────────────
/**
* GET /api/roles/user/my-groups
* 현재 로그인 사용자의 권한 그룹 조회
*/
@GetMapping("/user/my-groups")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMyRoleGroups(
@RequestAttribute("userId") String userId,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("userId", userId);
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(roleService.getUserRoleGroups(params), "사용자 권한 그룹 조회 성공"));
}
/**
* GET /api/roles/menus/all
* 전체 메뉴 목록 조회 (권한 설정용)
*/
@GetMapping("/menus/all")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAllMenus(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestParam(value = "companyCode", required = false) String requestedCompanyCode) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
Map<String, Object> params = new HashMap<>();
if (isSuperAdmin(role)) {
// SUPER_ADMIN: 쿼리 파라미터로 받은 companyCode 사용 (없으면 전체)
params.put("companyCode", requestedCompanyCode);
} else {
params.put("companyCode", companyCode);
}
return ResponseEntity.ok(ApiResponse.success(roleService.getAllMenus(params), "메뉴 목록 조회 성공"));
}
/**
* GET /api/roles/{id}/members
* 권한 그룹 멤버 목록 조회
*/
@GetMapping("/{id}/members")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getRoleMembers(
@PathVariable String id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
Map<String, Object> findParams = Map.of("objid", parseLong(id));
Map<String, Object> group = roleService.getRoleGroupById(findParams);
if (group == null) {
return ResponseEntity.status(404).body(ApiResponse.error("권한 그룹을 찾을 수 없습니다."));
}
if (!isSuperAdmin(role) && !companyCode.equals(group.get("companyCode"))) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("masterObjid", parseLong(id));
return ResponseEntity.ok(ApiResponse.success(roleService.getRoleMembers(params), "권한 그룹 멤버 조회 성공"));
}
/**
* POST /api/roles/{id}/members
* 권한 그룹 멤버 추가
*/
@PostMapping("/{id}/members")
public ResponseEntity<ApiResponse<Void>> addRoleMembers(
@PathVariable String id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestAttribute("userId") String currentUserId,
@RequestBody Map<String, Object> body) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
Map<String, Object> findParams = Map.of("objid", parseLong(id));
Map<String, Object> group = roleService.getRoleGroupById(findParams);
if (group == null) {
return ResponseEntity.status(404).body(ApiResponse.error("권한 그룹을 찾을 수 없습니다."));
}
if (!isSuperAdmin(role) && !companyCode.equals(group.get("companyCode"))) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다."));
}
Map<String, Object> params = new HashMap<>(body);
params.put("masterObjid", parseLong(id));
params.put("writer", currentUserId);
roleService.addRoleMembers(params);
return ResponseEntity.ok(ApiResponse.success(null, "권한 그룹 멤버 추가 성공"));
}
/**
* PUT /api/roles/{id}/members
* 권한 그룹 멤버 일괄 업데이트 (diff 방식)
*/
@PutMapping("/{id}/members")
public ResponseEntity<ApiResponse<Void>> updateRoleMembers(
@PathVariable String id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestAttribute("userId") String currentUserId,
@RequestBody Map<String, Object> body) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
Map<String, Object> findParams = Map.of("objid", parseLong(id));
Map<String, Object> group = roleService.getRoleGroupById(findParams);
if (group == null) {
return ResponseEntity.status(404).body(ApiResponse.error("권한 그룹을 찾을 수 없습니다."));
}
if (!isSuperAdmin(role) && !companyCode.equals(group.get("companyCode"))) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다."));
}
Map<String, Object> params = new HashMap<>(body);
params.put("masterObjid", parseLong(id));
params.put("writer", currentUserId);
roleService.updateRoleMembers(params);
return ResponseEntity.ok(ApiResponse.success(null, "권한 그룹 멤버 업데이트 성공"));
}
/**
* DELETE /api/roles/{id}/members
* 권한 그룹 멤버 제거
*/
@DeleteMapping("/{id}/members")
public ResponseEntity<ApiResponse<Void>> removeRoleMembers(
@PathVariable String id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestBody Map<String, Object> body) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
Map<String, Object> findParams = Map.of("objid", parseLong(id));
Map<String, Object> group = roleService.getRoleGroupById(findParams);
if (group == null) {
return ResponseEntity.status(404).body(ApiResponse.error("권한 그룹을 찾을 수 없습니다."));
}
if (!isSuperAdmin(role) && !companyCode.equals(group.get("companyCode"))) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다."));
}
Map<String, Object> params = new HashMap<>(body);
params.put("masterObjid", parseLong(id));
roleService.removeRoleMembers(params);
return ResponseEntity.ok(ApiResponse.success(null, "권한 그룹 멤버 제거 성공"));
}
// ──────────────────────────────────────────────────
// 메뉴 권한 관리
// ──────────────────────────────────────────────────
/**
* GET /api/roles/{id}/menu-permissions
* 메뉴 권한 목록 조회
*/
@GetMapping("/{id}/menu-permissions")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMenuPermissions(
@PathVariable String id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
Map<String, Object> findParams = Map.of("objid", parseLong(id));
Map<String, Object> group = roleService.getRoleGroupById(findParams);
if (group == null) {
return ResponseEntity.status(404).body(ApiResponse.error("권한 그룹을 찾을 수 없습니다."));
}
if (!isSuperAdmin(role) && !companyCode.equals(group.get("companyCode"))) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("authObjid", parseLong(id));
return ResponseEntity.ok(ApiResponse.success(roleService.getMenuPermissions(params), "메뉴 권한 조회 성공"));
}
/**
* PUT /api/roles/{id}/menu-permissions
* 메뉴 권한 설정 (전체 교체)
*/
@PutMapping("/{id}/menu-permissions")
public ResponseEntity<ApiResponse<Void>> setMenuPermissions(
@PathVariable String id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role,
@RequestAttribute("userId") String currentUserId,
@RequestBody Map<String, Object> body) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
Map<String, Object> findParams = Map.of("objid", parseLong(id));
Map<String, Object> group = roleService.getRoleGroupById(findParams);
if (group == null) {
return ResponseEntity.status(404).body(ApiResponse.error("권한 그룹을 찾을 수 없습니다."));
}
if (!isSuperAdmin(role) && !companyCode.equals(group.get("companyCode"))) {
return ResponseEntity.status(403).body(ApiResponse.error("권한이 없습니다."));
}
Map<String, Object> params = new HashMap<>(body);
params.put("authObjid", parseLong(id));
params.put("writer", currentUserId);
roleService.setMenuPermissions(params);
return ResponseEntity.ok(ApiResponse.success(null, "메뉴 권한 설정 성공"));
}
/**
* GET /api/roles/user/{userId}/groups
* 특정 사용자의 권한 그룹 조회
*/
@GetMapping("/user/{userId}/groups")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getUserRoleGroups(
@PathVariable String userId,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("role") String role) {
if (!isAdmin(role)) {
return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("userId", userId);
params.put("companyCode", isSuperAdmin(role) ? null : companyCode);
return ResponseEntity.ok(ApiResponse.success(roleService.getUserRoleGroups(params), "사용자 권한 그룹 조회 성공"));
}
// ──────────────────────────────────────────────────
// 내부 유틸
// ──────────────────────────────────────────────────
private boolean isSuperAdmin(String role) {
return "SUPER_ADMIN".equals(role);
}
private boolean isAdmin(String role) {
return "SUPER_ADMIN".equals(role) || "ADMIN".equals(role) || "COMPANY_ADMIN".equals(role);
}
private Long parseLong(String value) {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("유효하지 않은 ID입니다: " + value);
}
}
}
@@ -0,0 +1,74 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.SalesReportService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/sales-report")
@RequiredArgsConstructor
@Slf4j
public class SalesReportController {
private final SalesReportService salesReportService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getSalesReportList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(salesReportService.getSalesReportList(params)));
}
@GetMapping("/summary")
public ResponseEntity<ApiResponse<Map<String, Object>>> getSalesReportSummary(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(salesReportService.getSalesReportSummary(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getSalesReportInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(salesReportService.getSalesReportInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertSalesReport(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(salesReportService.insertSalesReport(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateSalesReport(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(salesReportService.updateSalesReport(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteSalesReport(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(salesReportService.deleteSalesReport(params)));
}
}
@@ -0,0 +1,107 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ScheduleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/schedule")
@RequiredArgsConstructor
@Slf4j
public class ScheduleController {
private final ScheduleService scheduleService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getScheduleList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(scheduleService.getScheduleList(params)));
}
@GetMapping("/{scheduleId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getScheduleInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long scheduleId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("scheduleId", scheduleId);
return ResponseEntity.ok(ApiResponse.success(scheduleService.getScheduleInfo(params)));
}
/**
* 스케줄 미리보기 (DB 저장 없음)
* POST /api/schedule/preview
*/
@PostMapping("/preview")
public ResponseEntity<ApiResponse<Map<String, Object>>> previewSchedule(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
try {
Map<String, Object> preview = scheduleService.previewSchedule(body);
return ResponseEntity.ok(ApiResponse.success(preview));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
}
}
/**
* 스케줄 적용 (DB 저장)
* POST /api/schedule/apply
*/
@PostMapping("/apply")
public ResponseEntity<ApiResponse<Map<String, Object>>> applySchedules(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
Map<String, Object> applied = scheduleService.applySchedules(body);
String message = applied.get("created") + "건 생성, " + applied.get("deleted") + "건 삭제, " + applied.get("updated") + "건 수정되었습니다.";
return ResponseEntity.ok(ApiResponse.success(applied, message));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
}
}
@PostMapping
public ResponseEntity<ApiResponse<Integer>> insertSchedule(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(scheduleService.insertSchedule(body)));
}
@PutMapping("/{scheduleId}")
public ResponseEntity<ApiResponse<Integer>> updateSchedule(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long scheduleId,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("scheduleId", scheduleId);
return ResponseEntity.ok(ApiResponse.success(scheduleService.updateSchedule(body)));
}
@DeleteMapping("/{scheduleId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteSchedule(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable Long scheduleId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("userId", userId);
params.put("scheduleId", scheduleId);
Map<String, Object> result = scheduleService.deleteSchedule(params);
if (Boolean.FALSE.equals(result.get("success"))) {
return ResponseEntity.status(404).body(ApiResponse.error((String) result.get("message")));
}
return ResponseEntity.ok(ApiResponse.success(result, "스케줄이 삭제되었습니다."));
}
}
@@ -0,0 +1,347 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ScreenEmbeddingService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 화면 임베딩 및 데이터 전달 시스템 컨트롤러
*
* GET /api/screen-embedding - 목록 조회 (?parentScreenId)
* GET /api/screen-embedding/{id} - 상세 조회
* POST /api/screen-embedding - 생성
* PUT /api/screen-embedding/{id} - 수정
* DELETE /api/screen-embedding/{id} - 삭제
*
* GET /api/screen-data-transfer - 조회 (?sourceScreenId&targetScreenId)
* POST /api/screen-data-transfer - 생성
* PUT /api/screen-data-transfer/{id} - 수정
* DELETE /api/screen-data-transfer/{id} - 삭제
*
* GET /api/screen-split-panel/{screenId} - 조회
* POST /api/screen-split-panel - 생성
* PUT /api/screen-split-panel/{id} - 수정
* DELETE /api/screen-split-panel/{id} - 삭제
*/
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
@Slf4j
public class ScreenEmbeddingController {
private final ScreenEmbeddingService service;
// ══════════════════════════════════════════════════════════════
// Screen Embedding
// ══════════════════════════════════════════════════════════════
@GetMapping("/screen-embedding")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getScreenEmbeddingList(
@RequestParam(required = false) Long parentScreenId,
@RequestAttribute("companyCode") String companyCode) {
if (parentScreenId == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("부모 화면 ID가 필요합니다."));
}
try {
Map<String, Object> params = new HashMap<>();
params.put("parentScreenId", parentScreenId);
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getScreenEmbeddingList(params)));
} catch (Exception e) {
log.error("화면 임베딩 목록 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 임베딩 목록 조회 중 오류가 발생했습니다."));
}
}
@GetMapping("/screen-embedding/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getScreenEmbeddingInfo(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
Map<String, Object> result = service.getScreenEmbeddingInfo(params);
if (result == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("화면 임베딩 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("화면 임베딩 상세 조회 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 임베딩 상세 조회 중 오류가 발생했습니다."));
}
}
@PostMapping("/screen-embedding")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertScreenEmbedding(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
if (body.get("parentScreenId") == null || body.get("childScreenId") == null
|| body.get("position") == null || body.get("mode") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("필수 필드가 누락되었습니다."));
}
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
Map<String, Object> result = service.insertScreenEmbedding(body);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(result));
} catch (DuplicateKeyException e) {
return ResponseEntity.status(409)
.body(ApiResponse.error("이미 동일한 임베딩 설정이 존재합니다."));
} catch (Exception e) {
log.error("화면 임베딩 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 임베딩 생성 중 오류가 발생했습니다."));
}
}
@PutMapping("/screen-embedding/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateScreenEmbedding(
@PathVariable Long id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
if (body.get("position") == null && body.get("mode") == null && body.get("config") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("수정할 내용이 없습니다."));
}
body.put("id", id);
body.put("companyCode", companyCode);
try {
Map<String, Object> result = service.updateScreenEmbedding(body);
if (result == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("화면 임베딩 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("화면 임베딩 수정 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 임베딩 수정 중 오류가 발생했습니다."));
}
}
@DeleteMapping("/screen-embedding/{id}")
public ResponseEntity<ApiResponse<Void>> deleteScreenEmbedding(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
try {
boolean deleted = service.deleteScreenEmbedding(params);
if (!deleted) {
return ResponseEntity.status(404)
.body(ApiResponse.error("화면 임베딩 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "화면 임베딩이 삭제되었습니다."));
} catch (Exception e) {
log.error("화면 임베딩 삭제 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 임베딩 삭제 중 오류가 발생했습니다."));
}
}
// ══════════════════════════════════════════════════════════════
// Screen Data Transfer
// ══════════════════════════════════════════════════════════════
@GetMapping("/screen-data-transfer")
public ResponseEntity<ApiResponse<Map<String, Object>>> getScreenDataTransferInfo(
@RequestParam(required = false) Long sourceScreenId,
@RequestParam(required = false) Long targetScreenId,
@RequestAttribute("companyCode") String companyCode) {
if (sourceScreenId == null || targetScreenId == null) {
return ResponseEntity.status(400)
.body(ApiResponse.error("소스 화면 ID와 타겟 화면 ID가 필요합니다."));
}
try {
Map<String, Object> params = new HashMap<>();
params.put("sourceScreenId", sourceScreenId);
params.put("targetScreenId", targetScreenId);
params.put("companyCode", companyCode);
Map<String, Object> result = service.getScreenDataTransferInfo(params);
if (result == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("데이터 전달 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("데이터 전달 설정 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("데이터 전달 설정 조회 중 오류가 발생했습니다."));
}
}
@PostMapping("/screen-data-transfer")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertScreenDataTransfer(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
if (body.get("sourceScreenId") == null || body.get("targetScreenId") == null
|| body.get("dataReceivers") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("필수 필드가 누락되었습니다."));
}
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
Map<String, Object> result = service.insertScreenDataTransfer(body);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(result));
} catch (DuplicateKeyException e) {
return ResponseEntity.status(409)
.body(ApiResponse.error("이미 동일한 데이터 전달 설정이 존재합니다."));
} catch (Exception e) {
log.error("데이터 전달 설정 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("데이터 전달 설정 생성 중 오류가 발생했습니다."));
}
}
@PutMapping("/screen-data-transfer/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateScreenDataTransfer(
@PathVariable Long id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
if (body.get("dataReceivers") == null && body.get("buttonConfig") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("수정할 내용이 없습니다."));
}
body.put("id", id);
body.put("companyCode", companyCode);
try {
Map<String, Object> result = service.updateScreenDataTransfer(body);
if (result == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("데이터 전달 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("데이터 전달 설정 수정 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("데이터 전달 설정 수정 중 오류가 발생했습니다."));
}
}
@DeleteMapping("/screen-data-transfer/{id}")
public ResponseEntity<ApiResponse<Void>> deleteScreenDataTransfer(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
try {
boolean deleted = service.deleteScreenDataTransfer(params);
if (!deleted) {
return ResponseEntity.status(404)
.body(ApiResponse.error("데이터 전달 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "데이터 전달 설정이 삭제되었습니다."));
} catch (Exception e) {
log.error("데이터 전달 설정 삭제 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("데이터 전달 설정 삭제 중 오류가 발생했습니다."));
}
}
// ══════════════════════════════════════════════════════════════
// Screen Split Panel
// ══════════════════════════════════════════════════════════════
@GetMapping("/screen-split-panel/{screenId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getScreenSplitPanelInfo(
@PathVariable Long screenId,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("screenId", screenId);
params.put("companyCode", companyCode);
Map<String, Object> result = service.getScreenSplitPanelInfo(params);
if (result == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("분할 패널 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("분할 패널 설정 조회 실패: screenId={}", screenId, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("분할 패널 설정 조회 중 오류가 발생했습니다."));
}
}
@PostMapping("/screen-split-panel")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertScreenSplitPanel(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
if (body.get("screenId") == null || body.get("leftEmbedding") == null
|| body.get("rightEmbedding") == null || body.get("dataTransfer") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("필수 필드가 누락되었습니다."));
}
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
Map<String, Object> result = service.insertScreenSplitPanel(body);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(result));
} catch (Exception e) {
log.error("분할 패널 설정 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("분할 패널 설정 생성 중 오류가 발생했습니다."));
}
}
@PutMapping("/screen-split-panel/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateScreenSplitPanel(
@PathVariable Long id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
if (body.get("layoutConfig") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("수정할 내용이 없습니다."));
}
body.put("id", id);
body.put("companyCode", companyCode);
try {
Map<String, Object> result = service.updateScreenSplitPanel(body);
if (result == null) {
return ResponseEntity.status(404)
.body(ApiResponse.error("분할 패널 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("분할 패널 설정 수정 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("분할 패널 설정 수정 중 오류가 발생했습니다."));
}
}
@DeleteMapping("/screen-split-panel/{id}")
public ResponseEntity<ApiResponse<Void>> deleteScreenSplitPanel(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
try {
boolean deleted = service.deleteScreenSplitPanel(params);
if (!deleted) {
return ResponseEntity.status(404)
.body(ApiResponse.error("분할 패널 설정을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "분할 패널 설정이 삭제되었습니다."));
} catch (Exception e) {
log.error("분할 패널 설정 삭제 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("분할 패널 설정 삭제 중 오류가 발생했습니다."));
}
}
}
@@ -0,0 +1,43 @@
package com.erp.controller;
import com.erp.service.ScreenFileService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/screens")
@RequiredArgsConstructor
@Slf4j
public class ScreenFileController {
private final ScreenFileService screenFileService;
/**
* GET /api/screens/{screenId}/components/files
* 화면의 모든 컴포넌트 파일 조회 (컴포넌트별 그룹화)
* 응답: { success, componentFiles, totalFiles, componentCount }
*/
@GetMapping("/{screenId}/components/files")
public ResponseEntity<Map<String, Object>> getScreenComponentFiles(
@PathVariable String screenId) {
Map<String, Object> result = screenFileService.getScreenComponentFiles(screenId);
return ResponseEntity.ok(result);
}
/**
* GET /api/screens/{screenId}/components/{componentId}/files
* 특정 컴포넌트의 파일 목록 조회
* 응답: { success, files, componentId, screenId }
*/
@GetMapping("/{screenId}/components/{componentId}/files")
public ResponseEntity<Map<String, Object>> getComponentFiles(
@PathVariable String screenId,
@PathVariable String componentId) {
Map<String, Object> result = screenFileService.getComponentFiles(screenId, componentId);
return ResponseEntity.ok(result);
}
}
@@ -0,0 +1,733 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ScreenGroupService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@RestController
@RequestMapping("/api/screen-groups")
@RequiredArgsConstructor
@Slf4j
public class ScreenGroupController {
private final ScreenGroupService service;
// ══════════════════════════════════════════════════════════════
// Screen Groups — CRUD
// ══════════════════════════════════════════════════════════════
/** GET /api/screen-groups/groups */
@GetMapping("/groups")
public ResponseEntity<?> getScreenGroups(
@RequestParam(required = false) String search,
@RequestParam(required = false) String groupName,
@RequestParam(required = false) Integer parentGroupId,
@RequestParam(required = false, defaultValue = "1") int page,
@RequestParam(required = false, defaultValue = "20") int size,
@RequestParam(required = false) String filterCompanyCode,
@RequestAttribute("companyCode") String companyCode) {
try {
String effectiveCompanyCode = ("*".equals(companyCode) && filterCompanyCode != null)
? filterCompanyCode : companyCode;
Map<String, Object> params = new HashMap<>();
params.put("companyCode", effectiveCompanyCode);
params.put("page", page);
params.put("size", size);
if (search != null) params.put("search", search);
if (groupName != null) params.put("groupName", groupName);
if (parentGroupId != null) params.put("parentGroupId", parentGroupId);
// 페이지네이션 응답은 Node.js 동일 구조(total/page/size/totalPages) 유지
return ResponseEntity.ok(service.getScreenGroups(params));
} catch (Exception e) {
log.error("화면 그룹 목록 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 그룹 목록 조회 중 오류가 발생했습니다"));
}
}
/** GET /api/screen-groups/groups/:id */
@GetMapping("/groups/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getScreenGroup(
@PathVariable Integer id,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
Map<String, Object> group = service.getScreenGroup(params);
if (group == null) {
return ResponseEntity.status(404).body(ApiResponse.error("화면 그룹을 찾을 수 없습니다"));
}
return ResponseEntity.ok(ApiResponse.success(group));
} catch (Exception e) {
log.error("화면 그룹 단건 조회 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 그룹 조회 중 오류가 발생했습니다"));
}
}
/** POST /api/screen-groups/groups */
@PostMapping("/groups")
public ResponseEntity<ApiResponse<Map<String, Object>>> createScreenGroup(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
Map<String, Object> created = service.createScreenGroup(body);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(created));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "화면 그룹 생성 중 오류가 발생했습니다"));
} catch (Exception e) {
log.error("화면 그룹 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 그룹 생성 중 오류가 발생했습니다"));
}
}
/** PUT /api/screen-groups/groups/:id */
@PutMapping("/groups/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateScreenGroup(
@PathVariable Integer id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("id", id);
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.ok(ApiResponse.success(service.updateScreenGroup(body)));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "화면 그룹을 찾을 수 없습니다"));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "화면 그룹 수정 중 오류가 발생했습니다"));
} catch (Exception e) {
log.error("화면 그룹 수정 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 그룹 수정 중 오류가 발생했습니다"));
}
}
/** DELETE /api/screen-groups/groups/:id */
@DeleteMapping("/groups/{id}")
public ResponseEntity<ApiResponse<Void>> deleteScreenGroup(
@PathVariable Integer id,
@RequestParam(required = false, defaultValue = "false") boolean deleteNumberingRules,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
params.put("userId", userId);
params.put("deleteNumberingRules", deleteNumberingRules);
try {
service.deleteScreenGroup(params);
return ResponseEntity.ok(ApiResponse.success(null, "화면 그룹이 삭제되었습니다"));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "화면 그룹을 찾을 수 없습니다"));
} catch (SecurityException e) {
return ResponseEntity.status(403).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "권한이 없습니다"));
} catch (Exception e) {
log.error("화면 그룹 삭제 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 그룹 삭제 중 오류가 발생했습니다"));
}
}
// ══════════════════════════════════════════════════════════════
// Screen Group Screens
// ══════════════════════════════════════════════════════════════
/** POST /api/screen-groups/group-screens */
@PostMapping("/group-screens")
public ResponseEntity<ApiResponse<Map<String, Object>>> addScreenToGroup(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(service.addScreenToGroup(body)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "화면 그룹 연결 중 오류가 발생했습니다"));
} catch (Exception e) {
log.error("화면 그룹 화면 추가 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 그룹 화면 추가 중 오류가 발생했습니다"));
}
}
/** PUT /api/screen-groups/group-screens/:id */
@PutMapping("/group-screens/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateScreenInGroup(
@PathVariable Integer id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("id", id);
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.ok(ApiResponse.success(service.updateScreenInGroup(body)));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "연결을 찾을 수 없습니다"));
} catch (Exception e) {
log.error("화면 그룹 화면 수정 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 그룹 화면 수정 중 오류가 발생했습니다"));
}
}
/** DELETE /api/screen-groups/group-screens/:id */
@DeleteMapping("/group-screens/{id}")
public ResponseEntity<ApiResponse<Void>> removeScreenFromGroup(
@PathVariable Integer id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
params.put("userId", userId);
try {
service.removeScreenFromGroup(params);
return ResponseEntity.ok(ApiResponse.success(null, "화면 그룹 연결이 해제되었습니다"));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "연결을 찾을 수 없습니다"));
} catch (Exception e) {
log.error("화면 그룹 화면 삭제 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면 그룹 화면 삭제 중 오류가 발생했습니다"));
}
}
// ══════════════════════════════════════════════════════════════
// Field Joins
// ══════════════════════════════════════════════════════════════
/** GET /api/screen-groups/field-joins */
@GetMapping("/field-joins")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFieldJoins(
@RequestParam(required = false) Integer groupId,
@RequestParam(required = false) Integer screenId,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
if (groupId != null) params.put("groupId", groupId);
if (screenId != null) params.put("screenId", screenId);
return ResponseEntity.ok(ApiResponse.success(service.getFieldJoins(params)));
} catch (Exception e) {
log.error("필드 조인 목록 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("필드 조인 목록 조회 중 오류가 발생했습니다"));
}
}
/** POST /api/screen-groups/field-joins */
@PostMapping("/field-joins")
public ResponseEntity<ApiResponse<Map<String, Object>>> createFieldJoin(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(service.createFieldJoin(body)));
} catch (Exception e) {
log.error("필드 조인 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("필드 조인 생성 중 오류가 발생했습니다"));
}
}
/** PUT /api/screen-groups/field-joins/:id */
@PutMapping("/field-joins/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateFieldJoin(
@PathVariable Integer id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("id", id);
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.ok(ApiResponse.success(service.updateFieldJoin(body)));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "필드 조인을 찾을 수 없습니다"));
} catch (Exception e) {
log.error("필드 조인 수정 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("필드 조인 수정 중 오류가 발생했습니다"));
}
}
/** DELETE /api/screen-groups/field-joins/:id */
@DeleteMapping("/field-joins/{id}")
public ResponseEntity<ApiResponse<Void>> deleteFieldJoin(
@PathVariable Integer id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
params.put("userId", userId);
try {
service.deleteFieldJoin(params);
return ResponseEntity.ok(ApiResponse.success(null, "필드 조인이 삭제되었습니다"));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "필드 조인을 찾을 수 없습니다"));
} catch (Exception e) {
log.error("필드 조인 삭제 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("필드 조인 삭제 중 오류가 발생했습니다"));
}
}
// ══════════════════════════════════════════════════════════════
// Data Flows
// ══════════════════════════════════════════════════════════════
/** GET /api/screen-groups/data-flows */
@GetMapping("/data-flows")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDataFlows(
@RequestParam(required = false) Integer groupId,
@RequestParam(required = false) Integer screenId,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
if (groupId != null) params.put("groupId", groupId);
if (screenId != null) params.put("screenId", screenId);
return ResponseEntity.ok(ApiResponse.success(service.getDataFlows(params)));
} catch (Exception e) {
log.error("데이터 흐름 목록 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("데이터 흐름 목록 조회 중 오류가 발생했습니다"));
}
}
/** POST /api/screen-groups/data-flows */
@PostMapping("/data-flows")
public ResponseEntity<ApiResponse<Map<String, Object>>> createDataFlow(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(service.createDataFlow(body)));
} catch (Exception e) {
log.error("데이터 흐름 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("데이터 흐름 생성 중 오류가 발생했습니다"));
}
}
/** PUT /api/screen-groups/data-flows/:id */
@PutMapping("/data-flows/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateDataFlow(
@PathVariable Integer id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("id", id);
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.ok(ApiResponse.success(service.updateDataFlow(body)));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "데이터 흐름을 찾을 수 없습니다"));
} catch (Exception e) {
log.error("데이터 흐름 수정 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("데이터 흐름 수정 중 오류가 발생했습니다"));
}
}
/** DELETE /api/screen-groups/data-flows/:id */
@DeleteMapping("/data-flows/{id}")
public ResponseEntity<ApiResponse<Void>> deleteDataFlow(
@PathVariable Integer id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
params.put("userId", userId);
try {
service.deleteDataFlow(params);
return ResponseEntity.ok(ApiResponse.success(null, "데이터 흐름이 삭제되었습니다"));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "데이터 흐름을 찾을 수 없습니다"));
} catch (Exception e) {
log.error("데이터 흐름 삭제 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("데이터 흐름 삭제 중 오류가 발생했습니다"));
}
}
// ══════════════════════════════════════════════════════════════
// Table Relations
// ══════════════════════════════════════════════════════════════
/** GET /api/screen-groups/table-relations */
@GetMapping("/table-relations")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTableRelations(
@RequestParam(required = false) Integer groupId,
@RequestParam(required = false) Integer screenId,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
if (groupId != null) params.put("groupId", groupId);
if (screenId != null) params.put("screenId", screenId);
return ResponseEntity.ok(ApiResponse.success(service.getTableRelations(params)));
} catch (Exception e) {
log.error("화면-테이블 관계 목록 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면-테이블 관계 목록 조회 중 오류가 발생했습니다"));
}
}
/** POST /api/screen-groups/table-relations */
@PostMapping("/table-relations")
public ResponseEntity<ApiResponse<Map<String, Object>>> createTableRelation(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(service.createTableRelation(body)));
} catch (Exception e) {
log.error("화면-테이블 관계 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면-테이블 관계 생성 중 오류가 발생했습니다"));
}
}
/** PUT /api/screen-groups/table-relations/:id */
@PutMapping("/table-relations/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateTableRelation(
@PathVariable Integer id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("id", id);
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.ok(ApiResponse.success(service.updateTableRelation(body)));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "화면-테이블 관계를 찾을 수 없습니다"));
} catch (Exception e) {
log.error("화면-테이블 관계 수정 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면-테이블 관계 수정 중 오류가 발생했습니다"));
}
}
/** DELETE /api/screen-groups/table-relations/:id */
@DeleteMapping("/table-relations/{id}")
public ResponseEntity<ApiResponse<Void>> deleteTableRelation(
@PathVariable Integer id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
params.put("userId", userId);
try {
service.deleteTableRelation(params);
return ResponseEntity.ok(ApiResponse.success(null, "화면-테이블 관계가 삭제되었습니다"));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "화면-테이블 관계를 찾을 수 없습니다"));
} catch (Exception e) {
log.error("화면-테이블 관계 삭제 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("화면-테이블 관계 삭제 중 오류가 발생했습니다"));
}
}
// ══════════════════════════════════════════════════════════════
// Layout Summary
// ══════════════════════════════════════════════════════════════
/** GET /api/screen-groups/layout-summary/:screenId */
@GetMapping("/layout-summary/{screenId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getScreenLayoutSummary(
@PathVariable Integer screenId,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("screenId", screenId);
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getScreenLayoutSummary(params)));
} catch (Exception e) {
log.error("레이아웃 요약 조회 실패: screenId={}", screenId, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("레이아웃 요약 조회 중 오류가 발생했습니다"));
}
}
/** POST /api/screen-groups/layout-summary/batch */
@PostMapping("/layout-summary/batch")
public ResponseEntity<ApiResponse<Map<String, Object>>> getMultipleScreenLayoutSummary(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
try {
@SuppressWarnings("unchecked")
List<Integer> screenIds = (List<Integer>) body.get("screenIds");
return ResponseEntity.ok(ApiResponse.success(
service.getMultipleScreenLayoutSummary(screenIds)));
} catch (Exception e) {
log.error("다중 레이아웃 요약 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("다중 레이아웃 요약 조회 중 오류가 발생했습니다"));
}
}
// ══════════════════════════════════════════════════════════════
// Sub Tables
// ══════════════════════════════════════════════════════════════
/** POST /api/screen-groups/sub-tables/batch */
@PostMapping("/sub-tables/batch")
public ResponseEntity<ApiResponse<Map<String, Object>>> getScreenSubTables(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
try {
@SuppressWarnings("unchecked")
List<Integer> screenIds = (List<Integer>) body.get("screenIds");
return ResponseEntity.ok(ApiResponse.success(
service.getScreenSubTables(screenIds)));
} catch (Exception e) {
log.error("서브 테이블 배치 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("서브 테이블 조회 중 오류가 발생했습니다"));
}
}
// ══════════════════════════════════════════════════════════════
// Sync
// ══════════════════════════════════════════════════════════════
/** GET /api/screen-groups/sync/status */
@GetMapping("/sync/status")
public ResponseEntity<ApiResponse<Map<String, Object>>> getSyncStatus(
@RequestParam(required = false) String filterCompanyCode,
@RequestAttribute("companyCode") String companyCode) {
try {
String effectiveCompanyCode = ("*".equals(companyCode) && filterCompanyCode != null)
? filterCompanyCode : companyCode;
return ResponseEntity.ok(ApiResponse.success(service.getSyncStatus(effectiveCompanyCode)));
} catch (Exception e) {
log.error("동기화 상태 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("동기화 상태 조회 중 오류가 발생했습니다"));
}
}
/** POST /api/screen-groups/sync/screen-to-menu */
@PostMapping("/sync/screen-to-menu")
public ResponseEntity<ApiResponse<Map<String, Object>>> syncScreenGroupsToMenu(
@RequestBody(required = false) Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
try {
String targetCompanyCode = companyCode;
if (body != null && body.get("companyCode") != null) {
targetCompanyCode = (String) body.get("companyCode");
}
Map<String, Object> result = service.syncScreenGroupsToMenu(targetCompanyCode, userId);
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("화면관리 → 메뉴 동기화 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("동기화 중 오류가 발생했습니다"));
}
}
/** POST /api/screen-groups/sync/menu-to-screen */
@PostMapping("/sync/menu-to-screen")
public ResponseEntity<ApiResponse<Map<String, Object>>> syncMenuToScreenGroups(
@RequestBody(required = false) Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
try {
String targetCompanyCode = companyCode;
if (body != null && body.get("companyCode") != null) {
targetCompanyCode = (String) body.get("companyCode");
}
Map<String, Object> result = service.syncMenuToScreenGroups(targetCompanyCode, userId);
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("메뉴 → 화면관리 동기화 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("동기화 중 오류가 발생했습니다"));
}
}
/** POST /api/screen-groups/sync/all */
@PostMapping("/sync/all")
public ResponseEntity<ApiResponse<Map<String, Object>>> syncAllCompanies(
@RequestAttribute("userId") String userId) {
try {
return ResponseEntity.ok(ApiResponse.success(service.syncAllCompanies(userId)));
} catch (Exception e) {
log.error("전체 회사 동기화 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("전체 회사 동기화 중 오류가 발생했습니다"));
}
}
// ══════════════════════════════════════════════════════════════
// POP Groups
// ══════════════════════════════════════════════════════════════
/** GET /api/screen-groups/pop/groups */
@GetMapping("/pop/groups")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getPopScreenGroups(
@RequestParam(required = false) String filterCompanyCode,
@RequestAttribute("companyCode") String companyCode) {
try {
String effectiveCompanyCode = ("*".equals(companyCode) && filterCompanyCode != null)
? filterCompanyCode : companyCode;
Map<String, Object> params = new HashMap<>();
params.put("companyCode", effectiveCompanyCode);
return ResponseEntity.ok(ApiResponse.success(service.getPopScreenGroups(params)));
} catch (Exception e) {
log.error("POP 화면 그룹 목록 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("POP 화면 그룹 목록 조회 중 오류가 발생했습니다"));
}
}
/** POST /api/screen-groups/pop/groups */
@PostMapping("/pop/groups")
public ResponseEntity<ApiResponse<Map<String, Object>>> createPopScreenGroup(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("userCompanyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(service.createPopScreenGroup(body)));
} catch (SecurityException e) {
return ResponseEntity.status(403).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "권한이 없습니다"));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "POP 화면 그룹 생성 중 오류가 발생했습니다"));
} catch (Exception e) {
log.error("POP 화면 그룹 생성 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("POP 화면 그룹 생성 중 오류가 발생했습니다"));
}
}
/** PUT /api/screen-groups/pop/groups/:id */
@PutMapping("/pop/groups/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updatePopScreenGroup(
@PathVariable Integer id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("id", id);
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.ok(ApiResponse.success(service.updatePopScreenGroup(body)));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "그룹을 찾을 수 없습니다"));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "POP 화면 그룹 수정 중 오류가 발생했습니다"));
} catch (Exception e) {
log.error("POP 화면 그룹 수정 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("POP 화면 그룹 수정 중 오류가 발생했습니다"));
}
}
/** DELETE /api/screen-groups/pop/groups/:id */
@DeleteMapping("/pop/groups/{id}")
public ResponseEntity<ApiResponse<Void>> deletePopScreenGroup(
@PathVariable Integer id,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
params.put("userId", userId);
try {
service.deletePopScreenGroup(params);
return ResponseEntity.ok(ApiResponse.success(null, "POP 화면 그룹이 삭제되었습니다"));
} catch (NoSuchElementException e) {
return ResponseEntity.status(404).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "그룹을 찾을 수 없습니다"));
} catch (SecurityException e) {
return ResponseEntity.status(403).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "권한이 없습니다"));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(400).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "POP 화면 그룹 삭제 중 오류가 발생했습니다"));
} catch (Exception e) {
log.error("POP 화면 그룹 삭제 실패: id={}", id, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("POP 화면 그룹 삭제 중 오류가 발생했습니다"));
}
}
/** POST /api/screen-groups/pop/ensure-root */
@PostMapping("/pop/ensure-root")
public ResponseEntity<ApiResponse<Map<String, Object>>> ensurePopRootGroup(
@RequestBody(required = false) Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
try {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("userId", userId);
if (body != null) params.putAll(body);
Map<String, Object> result = service.ensurePopRootGroup(params);
return ResponseEntity.ok(ApiResponse.success(result));
} catch (Exception e) {
log.error("POP 루트 그룹 확보 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("POP 루트 그룹 확보 중 오류가 발생했습니다"));
}
}
}
@@ -0,0 +1,653 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ScreenManagementService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/screen-management")
@RequiredArgsConstructor
@Slf4j
public class ScreenManagementController {
private final ScreenManagementService service;
// 화면 목록 / 단건
@GetMapping("/screens")
public ResponseEntity<ApiResponse<Map<String, Object>>> getScreens(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", params.getOrDefault("companyCode", companyCode));
// 권한 확인: 최고 관리자가 아니면 자사 companyCode만 허용
String targetCode = (String) params.get("companyCode");
if (!"*".equals(companyCode) && !companyCode.equals(targetCode)) {
return ResponseEntity.status(403)
.body(ApiResponse.error("다른 회사의 화면을 조회할 권한이 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(service.getScreens(params)));
}
@GetMapping("/screens/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getScreen(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("id") Integer screenId) {
Map<String, Object> screen = service.getScreenById(screenId);
if (screen == null) {
return ResponseEntity.status(404).body(ApiResponse.error("화면을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(screen));
}
@GetMapping("/screens/{id}/menu")
public ResponseEntity<ApiResponse<Map<String, Object>>> getScreenMenu(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("id") Integer screenId) {
Map<String, Object> menuInfo = service.getMenuByScreen(screenId, companyCode);
return ResponseEntity.ok(ApiResponse.success(menuInfo));
}
// 화면 생성 / 수정
@PostMapping("/screens")
public ResponseEntity<ApiResponse<Map<String, Object>>> createScreen(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> screen = service.createScreen(body, companyCode, userId);
return ResponseEntity.status(201).body(ApiResponse.success(screen, "화면이 생성되었습니다."));
} catch (IllegalStateException e) {
return ResponseEntity.status(409).body(ApiResponse.error("화면 코드가 이미 존재합니다."));
} catch (Exception e) {
log.error("화면 생성 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 생성에 실패했습니다."));
}
}
@PutMapping("/screens/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateScreen(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("id") Integer screenId,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> screen = service.updateScreen(screenId, body, companyCode, userId);
if (screen == null) return ResponseEntity.status(404).body(ApiResponse.error("화면을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(screen, "화면이 수정되었습니다."));
} catch (Exception e) {
log.error("화면 수정 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 수정에 실패했습니다."));
}
}
@PutMapping("/screens/{id}/info")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateScreenInfo(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("id") Integer screenId,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> screen = service.updateScreenInfo(screenId, body, companyCode, userId);
if (screen == null) return ResponseEntity.status(404).body(ApiResponse.error("화면을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(screen, "화면 정보가 수정되었습니다."));
} catch (Exception e) {
log.error("화면 정보 수정 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 정보 수정에 실패했습니다."));
}
}
@PatchMapping("/screens/{screenId}/table-name")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateScreenTableName(
@RequestAttribute("userId") String userId,
@PathVariable Integer screenId,
@RequestBody Map<String, Object> body) {
String tableName = (String) body.get("tableName");
Map<String, Object> screen = service.updateScreenTableName(screenId, tableName, userId);
if (screen == null) return ResponseEntity.status(404).body(ApiResponse.error("화면을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(screen));
}
// 의존성 / 연결 모달
@GetMapping("/screens/{id}/dependencies")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkScreenDependencies(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("id") Integer screenId) {
Map<String, Object> deps = service.checkScreenDependencies(screenId, companyCode);
return ResponseEntity.ok(ApiResponse.success(deps));
}
@GetMapping("/screens/{id}/linked-modals")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> detectLinkedScreens(
@RequestAttribute("companyCode") String companyCode,
@PathVariable("id") Integer screenId) {
List<Map<String, Object>> linked = service.detectLinkedScreens(screenId, companyCode);
return ResponseEntity.ok(ApiResponse.success(linked));
}
// 삭제
@DeleteMapping("/screens/{id}")
public ResponseEntity<ApiResponse<Void>> deleteScreen(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("id") Integer screenId,
@RequestParam(required = false) String deleteReason) {
try {
service.softDeleteScreen(screenId, userId, companyCode, deleteReason);
return ResponseEntity.ok(ApiResponse.success(null, "화면이 삭제되었습니다."));
} catch (Exception e) {
log.error("화면 삭제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 삭제에 실패했습니다."));
}
}
@DeleteMapping("/screens/bulk/delete")
public ResponseEntity<ApiResponse<Void>> bulkDeleteScreens(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
@SuppressWarnings("unchecked")
List<Integer> screenIds = (List<Integer>) body.get("screenIds");
try {
service.bulkSoftDeleteScreens(screenIds, userId, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, screenIds.size() + "개 화면이 삭제되었습니다."));
} catch (Exception e) {
log.error("화면 일괄 삭제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 일괄 삭제에 실패했습니다."));
}
}
// 중복 체크
@PostMapping("/screens/check-duplicate-name")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkDuplicateScreenName(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
String screenName = (String) body.get("screenName");
String targetCode = body.containsKey("companyCode") ? (String) body.get("companyCode") : companyCode;
boolean isDuplicate = service.checkDuplicateScreenName(screenName, targetCode);
return ResponseEntity.ok(ApiResponse.success(Map.of("isDuplicate", isDuplicate)));
}
// 복사
@PostMapping("/screens/{id}/copy")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyScreen(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("id") Integer screenId,
@RequestBody(required = false) Map<String, Object> body) {
try {
String newName = body != null ? (String) body.get("newName") : null;
Map<String, Object> screen = service.copyScreen(screenId, newName, companyCode, userId);
return ResponseEntity.status(201).body(ApiResponse.success(screen, "화면이 복사되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("화면 복사 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 복사에 실패했습니다."));
}
}
@PostMapping("/screens/{id}/copy-with-modals")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyScreenWithModals(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("id") Integer screenId,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> result = service.copyScreenWithModals(screenId, body, companyCode, userId);
return ResponseEntity.status(201).body(ApiResponse.success(result, "화면과 모달이 복사되었습니다."));
} catch (Exception e) {
log.error("화면 + 모달 복사 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 복사에 실패했습니다."));
}
}
// 휴지통
@GetMapping("/screens/trash/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDeletedScreens(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getDeletedScreens(params)));
}
@PostMapping("/screens/{id}/restore")
public ResponseEntity<ApiResponse<Map<String, Object>>> restoreScreen(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable("id") Integer screenId) {
try {
Map<String, Object> screen = service.restoreScreen(screenId, userId, companyCode);
return ResponseEntity.ok(ApiResponse.success(screen, "화면이 복원되었습니다."));
} catch (IllegalStateException e) {
return ResponseEntity.status(409).body(ApiResponse.error("화면 코드가 이미 사용 중입니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("화면 복원 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 복원에 실패했습니다."));
}
}
@DeleteMapping("/screens/{id}/permanent")
public ResponseEntity<ApiResponse<Void>> permanentDeleteScreen(
@PathVariable("id") Integer screenId) {
try {
service.permanentDeleteScreen(screenId);
return ResponseEntity.ok(ApiResponse.success(null, "화면이 영구 삭제되었습니다."));
} catch (Exception e) {
log.error("화면 영구 삭제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 영구 삭제에 실패했습니다."));
}
}
@DeleteMapping("/screens/trash/bulk")
public ResponseEntity<ApiResponse<Void>> bulkPermanentDeleteScreens(
@RequestBody Map<String, Object> body) {
@SuppressWarnings("unchecked")
List<Integer> screenIds = (List<Integer>) body.get("screenIds");
try {
service.bulkPermanentDeleteScreens(screenIds);
return ResponseEntity.ok(ApiResponse.success(null, screenIds.size() + "개 화면이 영구 삭제되었습니다."));
} catch (Exception e) {
log.error("화면 일괄 영구 삭제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 일괄 영구 삭제에 실패했습니다."));
}
}
// 화면 코드 생성
@GetMapping("/generate-screen-code/{companyCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> generateScreenCode(
@PathVariable String companyCode) {
try {
String code = service.generateScreenCode(companyCode);
return ResponseEntity.ok(ApiResponse.success(Map.of("screenCode", code)));
} catch (Exception e) {
log.error("화면 코드 생성 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 코드 생성에 실패했습니다."));
}
}
@PostMapping("/generate-screen-codes")
public ResponseEntity<ApiResponse<Map<String, Object>>> generateMultipleScreenCodes(
@RequestBody Map<String, Object> body) {
String companyCode = (String) body.get("companyCode");
int count = body.get("count") != null ? Integer.parseInt(body.get("count").toString()) : 1;
try {
List<String> codes = service.generateMultipleScreenCodes(companyCode, count);
return ResponseEntity.ok(ApiResponse.success(Map.of("screenCodes", codes)));
} catch (Exception e) {
log.error("화면 코드 일괄 생성 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("화면 코드 생성에 실패했습니다."));
}
}
// 테이블 관리
@GetMapping("/tables")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTables() {
return ResponseEntity.ok(ApiResponse.success(service.getTables()));
}
@GetMapping("/tables/{tableName}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTableInfo(
@PathVariable String tableName) {
Map<String, Object> info = service.getTableInfo(tableName);
if (info == null) return ResponseEntity.status(404).body(ApiResponse.error("테이블을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(info));
}
@GetMapping("/tables/{tableName}/columns")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTableColumns(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName) {
return ResponseEntity.ok(ApiResponse.success(service.getTableColumns(tableName, companyCode)));
}
// 레이아웃 V1
@PostMapping("/screens/{screenId}/layout")
public ResponseEntity<ApiResponse<Void>> saveLayout(
@RequestAttribute("userId") String userId,
@PathVariable Integer screenId,
@RequestBody Map<String, Object> body) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> components = (List<Map<String, Object>>) body.get("components");
if (components == null) components = List.of();
try {
service.saveLayout(screenId, components, userId);
return ResponseEntity.ok(ApiResponse.success(null, "레이아웃이 저장되었습니다."));
} catch (Exception e) {
log.error("레이아웃 저장 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("레이아웃 저장에 실패했습니다."));
}
}
@GetMapping("/screens/{screenId}/layout")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getLayout(
@PathVariable Integer screenId) {
return ResponseEntity.ok(ApiResponse.success(service.getLayoutV1(screenId)));
}
@GetMapping("/screens/{screenId}/layout-v1")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getLayoutV1(
@PathVariable Integer screenId) {
return ResponseEntity.ok(ApiResponse.success(service.getLayoutV1(screenId)));
}
// 레이아웃 V2
@GetMapping("/screens/{screenId}/layout-v2")
public ResponseEntity<ApiResponse<Map<String, Object>>> getLayoutV2(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId,
@RequestParam(required = false) Integer layerId) {
Map<String, Object> layout = service.getLayoutV2(screenId, companyCode, layerId);
return ResponseEntity.ok(ApiResponse.success(layout));
}
@PostMapping("/screens/{screenId}/layout-v2")
public ResponseEntity<ApiResponse<Void>> saveLayoutV2(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable Integer screenId,
@RequestBody Map<String, Object> body) {
try {
service.saveLayoutV2(screenId, body, companyCode, userId);
return ResponseEntity.ok(ApiResponse.success(null, "레이아웃 V2가 저장되었습니다."));
} catch (Exception e) {
log.error("레이아웃 V2 저장 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("레이아웃 저장에 실패했습니다."));
}
}
// POP 레이아웃
@GetMapping("/screens/{screenId}/layout-pop")
public ResponseEntity<ApiResponse<Map<String, Object>>> getLayoutPop(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId) {
return ResponseEntity.ok(ApiResponse.success(service.getLayoutPop(screenId, companyCode)));
}
@PostMapping("/screens/{screenId}/layout-pop")
public ResponseEntity<ApiResponse<Void>> saveLayoutPop(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable Integer screenId,
@RequestBody Map<String, Object> body) {
try {
service.saveLayoutPop(screenId, body, companyCode, userId);
return ResponseEntity.ok(ApiResponse.success(null, "POP 레이아웃이 저장되었습니다."));
} catch (Exception e) {
log.error("POP 레이아웃 저장 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("POP 레이아웃 저장에 실패했습니다."));
}
}
@DeleteMapping("/screens/{screenId}/layout-pop")
public ResponseEntity<ApiResponse<Void>> deleteLayoutPop(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId) {
service.deleteLayoutPop(screenId, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "POP 레이아웃이 삭제되었습니다."));
}
@GetMapping("/pop-layout-screen-ids")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getScreenIdsWithPopLayout(
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(service.getScreenIdsWithPopLayout(companyCode)));
}
// 레이어 관리
@GetMapping("/screens/{screenId}/layers")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getScreenLayers(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId) {
return ResponseEntity.ok(ApiResponse.success(service.getScreenLayers(screenId, companyCode)));
}
@GetMapping("/screens/{screenId}/layers/{layerId}/layout")
public ResponseEntity<ApiResponse<Map<String, Object>>> getLayerLayout(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId,
@PathVariable Integer layerId) {
Map<String, Object> layout = service.getLayerLayout(screenId, layerId, companyCode);
return ResponseEntity.ok(ApiResponse.success(layout));
}
@DeleteMapping("/screens/{screenId}/layers/{layerId}")
public ResponseEntity<ApiResponse<Void>> deleteLayer(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId,
@PathVariable Integer layerId) {
boolean deleted = service.deleteLayer(screenId, layerId, companyCode);
if (!deleted) return ResponseEntity.status(404).body(ApiResponse.error("레이어를 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null, "레이어가 삭제되었습니다."));
}
@PutMapping("/screens/{screenId}/layers/{layerId}/condition")
public ResponseEntity<ApiResponse<Void>> updateLayerCondition(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId,
@PathVariable Integer layerId,
@RequestBody Map<String, Object> body) {
service.updateLayerCondition(screenId, layerId, body, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "레이어 조건이 업데이트되었습니다."));
}
// Zone 관리
@GetMapping("/screens/{screenId}/zones")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getScreenZones(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId) {
return ResponseEntity.ok(ApiResponse.success(service.getScreenZones(screenId, companyCode)));
}
@PostMapping("/screens/{screenId}/zones")
public ResponseEntity<ApiResponse<Map<String, Object>>> createZone(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> result = service.createZone(screenId, body, companyCode);
return ResponseEntity.status(201).body(ApiResponse.success(result, "Zone이 생성되었습니다."));
} catch (Exception e) {
log.error("Zone 생성 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("Zone 생성에 실패했습니다."));
}
}
@PutMapping("/zones/{zoneId}")
public ResponseEntity<ApiResponse<Void>> updateZone(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer zoneId,
@RequestBody Map<String, Object> body) {
boolean updated = service.updateZone(zoneId, body, companyCode);
if (!updated) return ResponseEntity.status(404).body(ApiResponse.error("Zone을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null, "Zone이 수정되었습니다."));
}
@DeleteMapping("/zones/{zoneId}")
public ResponseEntity<ApiResponse<Void>> deleteZone(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer zoneId) {
boolean deleted = service.deleteZone(zoneId, companyCode);
if (!deleted) return ResponseEntity.status(404).body(ApiResponse.error("Zone을 찾을 수 없습니다."));
return ResponseEntity.ok(ApiResponse.success(null, "Zone이 삭제되었습니다."));
}
@PostMapping("/screens/{screenId}/zones/{zoneId}/layers")
public ResponseEntity<ApiResponse<Map<String, Object>>> addLayerToZone(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId,
@PathVariable Integer zoneId,
@RequestBody(required = false) Map<String, Object> body) {
if (body == null) body = Map.of();
try {
Map<String, Object> result = service.addLayerToZone(screenId, zoneId, body, companyCode);
return ResponseEntity.status(201).body(ApiResponse.success(result, "레이어가 추가되었습니다."));
} catch (Exception e) {
log.error("레이어 추가 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("레이어 추가에 실패했습니다."));
}
}
// 메뉴-화면 할당
@PostMapping("/screens/{screenId}/assign-menu")
public ResponseEntity<ApiResponse<Map<String, Object>>> assignScreenToMenu(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@PathVariable Integer screenId,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> result = service.assignScreenToMenu(screenId, body, companyCode, userId);
return ResponseEntity.status(201).body(ApiResponse.success(result, "메뉴에 화면이 할당되었습니다."));
} catch (IllegalStateException e) {
return ResponseEntity.status(409).body(ApiResponse.error("이미 할당된 메뉴입니다."));
} catch (Exception e) {
log.error("메뉴 할당 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("메뉴 할당에 실패했습니다."));
}
}
@GetMapping("/menus/{menuObjid}/screens")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getScreensByMenu(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer menuObjid) {
return ResponseEntity.ok(ApiResponse.success(service.getScreensByMenu(menuObjid, companyCode)));
}
@DeleteMapping("/screens/{screenId}/menus/{menuObjid}")
public ResponseEntity<ApiResponse<Void>> unassignScreenFromMenu(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId,
@PathVariable Integer menuObjid) {
try {
service.unassignScreenFromMenu(screenId, menuObjid, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "메뉴 할당이 해제되었습니다."));
} catch (Exception e) {
log.error("메뉴 할당 해제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("메뉴 할당 해제에 실패했습니다."));
}
}
@PostMapping("/admin/cleanup-deleted-screen-menu-assignments")
public ResponseEntity<ApiResponse<Map<String, Object>>> cleanupDeletedScreenMenuAssignments() {
int count = service.cleanupDeletedMenuAssignments();
return ResponseEntity.ok(ApiResponse.success(Map.of("count", count), count + "개 메뉴 할당이 정리되었습니다."));
}
// 참조 업데이트
@PostMapping("/screens/update-tab-references")
public ResponseEntity<ApiResponse<Void>> updateTabScreenReferences(
@RequestBody Map<String, Object> body) {
try {
service.updateTabScreenReferences(body);
return ResponseEntity.ok(ApiResponse.success(null, "탭 참조가 업데이트되었습니다."));
} catch (Exception e) {
log.error("탭 참조 업데이트 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("탭 참조 업데이트에 실패했습니다."));
}
}
// 복제 작업
@PostMapping("/copy-menu-assignments")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyScreenMenuAssignments(
@RequestBody Map<String, Object> body) {
try {
int count = service.copyScreenMenuAssignments(body);
return ResponseEntity.ok(ApiResponse.success(Map.of("count", count)));
} catch (Exception e) {
log.error("메뉴 할당 복제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("메뉴 할당 복제에 실패했습니다."));
}
}
@PostMapping("/copy-code-category")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyCodeCategoryAndCodes(
@RequestBody Map<String, Object> body) {
try {
int count = service.copyCodeCategoryAndCodes(body);
return ResponseEntity.ok(ApiResponse.success(Map.of("count", count)));
} catch (Exception e) {
log.error("코드 카테고리 복제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("코드 카테고리 복제에 실패했습니다."));
}
}
@PostMapping("/copy-category-mapping")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyCategoryMapping(
@RequestBody Map<String, Object> body) {
try {
int count = service.copyCategoryMapping(body);
return ResponseEntity.ok(ApiResponse.success(Map.of("count", count)));
} catch (Exception e) {
log.error("카테고리 매핑 복제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("카테고리 매핑 복제에 실패했습니다."));
}
}
@PostMapping("/copy-table-type-columns")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyTableTypeColumns(
@RequestBody Map<String, Object> body) {
try {
int count = service.copyTableTypeColumns(body);
return ResponseEntity.ok(ApiResponse.success(Map.of("count", count)));
} catch (Exception e) {
log.error("테이블 타입 컬럼 복제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("테이블 타입 컬럼 복제에 실패했습니다."));
}
}
@PostMapping("/copy-cascading-relation")
public ResponseEntity<ApiResponse<Map<String, Object>>> copyCascadingRelation(
@RequestBody Map<String, Object> body) {
try {
int count = service.copyCascadingRelation(body);
return ResponseEntity.ok(ApiResponse.success(Map.of("count", count)));
} catch (Exception e) {
log.error("연쇄관계 복제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("연쇄관계 복제에 실패했습니다."));
}
}
// POP 화면 배포
@GetMapping("/screens/{screenId}/pop-links")
public ResponseEntity<ApiResponse<Map<String, Object>>> analyzePopScreenLinks(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Integer screenId) {
return ResponseEntity.ok(ApiResponse.success(service.analyzePopScreenLinks(screenId, companyCode)));
}
@PostMapping("/deploy-pop-screens")
public ResponseEntity<ApiResponse<Map<String, Object>>> deployPopScreens(
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
try {
Map<String, Object> result = service.deployPopScreens(body, userId);
return ResponseEntity.ok(ApiResponse.success(result, "POP 화면 배포가 완료되었습니다."));
} catch (Exception e) {
log.error("POP 화면 배포 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("POP 화면 배포에 실패했습니다."));
}
}
}
@@ -0,0 +1,63 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ScreenStandardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/screen-standard")
@RequiredArgsConstructor
@Slf4j
public class ScreenStandardController {
private final ScreenStandardService screenStandardService;
/**
* GET /api/screen-standard/web-types
* 웹타입 목록 조회 (active / category / search 필터)
*/
@GetMapping("/web-types")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getWebTypeList(
@RequestParam Map<String, Object> params) {
List<Map<String, Object>> data = screenStandardService.getWebTypeList(params);
return ResponseEntity.ok(ApiResponse.success(data, "웹타입 목록을 성공적으로 조회했습니다."));
}
/**
* GET /api/screen-standard/web-types/categories
* 웹타입 카테고리 목록 조회
* NOTE: /web-types/{webType} 경로가 없으므로 순서 무관하지만 명확성을 위해 먼저 선언
*/
@GetMapping("/web-types/categories")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getWebTypeCategoryList() {
List<Map<String, Object>> data = screenStandardService.getWebTypeCategoryList();
return ResponseEntity.ok(ApiResponse.success(data, "웹타입 카테고리 목록을 성공적으로 조회했습니다."));
}
/**
* GET /api/screen-standard/button-actions
* 버튼 액션 목록 조회 (active / category / search 필터)
*/
@GetMapping("/button-actions")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getButtonActionList(
@RequestParam Map<String, Object> params) {
List<Map<String, Object>> data = screenStandardService.getButtonActionList(params);
return ResponseEntity.ok(ApiResponse.success(data, "버튼 액션 목록을 성공적으로 조회했습니다."));
}
/**
* GET /api/screen-standard/button-actions/categories
* 버튼 액션 카테고리 목록 조회
*/
@GetMapping("/button-actions/categories")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getButtonActionCategoryList() {
List<Map<String, Object>> data = screenStandardService.getButtonActionCategoryList();
return ResponseEntity.ok(ApiResponse.success(data, "버튼 액션 카테고리 목록을 성공적으로 조회했습니다."));
}
}
@@ -0,0 +1,62 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ShippingOrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/shipping-order")
@RequiredArgsConstructor
@Slf4j
public class ShippingOrderController {
private final ShippingOrderService shippingOrderService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getShippingOrderList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(shippingOrderService.getShippingOrderList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getShippingOrderInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(shippingOrderService.getShippingOrderInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertShippingOrder(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(shippingOrderService.insertShippingOrder(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateShippingOrder(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(shippingOrderService.updateShippingOrder(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteShippingOrder(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(shippingOrderService.deleteShippingOrder(params)));
}
}
@@ -0,0 +1,62 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.ShippingPlanService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/shipping-plan")
@RequiredArgsConstructor
@Slf4j
public class ShippingPlanController {
private final ShippingPlanService shippingPlanService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getShippingPlanList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(shippingPlanService.getShippingPlanList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getShippingPlanInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(shippingPlanService.getShippingPlanInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertShippingPlan(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(shippingPlanService.insertShippingPlan(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateShippingPlan(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(shippingPlanService.updateShippingPlan(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteShippingPlan(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(shippingPlanService.deleteShippingPlan(params)));
}
}
@@ -0,0 +1,128 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.SystemNoticeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/system-notices")
@RequiredArgsConstructor
@Slf4j
public class SystemNoticeController {
private final SystemNoticeService systemNoticeService;
/**
* GET /api/system-notices
* 공지사항 목록 조회
*/
@GetMapping
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getSystemNoticeList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> queryParams) {
queryParams.put("companyCode", companyCode);
List<Map<String, Object>> list = systemNoticeService.getSystemNoticeList(queryParams);
return ResponseEntity.ok(ApiResponse.success(list));
}
/**
* POST /api/system-notices
* 공지사항 등록
*/
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertSystemNotice(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
String title = (String) body.get("title");
String content = (String) body.get("content");
if (title == null || title.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("제목을 입력해주세요."));
}
if (content == null || content.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("내용을 입력해주세요."));
}
Map<String, Object> params = new HashMap<>(body);
params.put("companyCode", companyCode);
params.put("createdBy", userId);
try {
Map<String, Object> created = systemNoticeService.insertSystemNotice(params);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(created, "공지사항이 등록되었습니다."));
} catch (Exception e) {
log.error("공지사항 등록 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("공지사항 등록 중 오류가 발생했습니다."));
}
}
/**
* PUT /api/system-notices/:id
* 공지사항 수정
*/
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateSystemNotice(
@PathVariable Long id,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
String title = (String) body.get("title");
String content = (String) body.get("content");
if (title == null || title.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("제목을 입력해주세요."));
}
if (content == null || content.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("내용을 입력해주세요."));
}
Map<String, Object> params = new HashMap<>(body);
params.put("id", id);
params.put("companyCode", companyCode);
try {
Map<String, Object> updated = systemNoticeService.updateSystemNotice(params);
return ResponseEntity.ok(ApiResponse.success(updated, "공지사항이 수정되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("공지사항 수정 실패: id={}", id, e);
return ResponseEntity.status(500).body(ApiResponse.error("공지사항 수정 중 오류가 발생했습니다."));
}
}
/**
* DELETE /api/system-notices/:id
* 공지사항 삭제
*/
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteSystemNotice(
@PathVariable Long id,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("companyCode", companyCode);
try {
systemNoticeService.deleteSystemNotice(params);
return ResponseEntity.ok(ApiResponse.success(null, "공지사항이 삭제되었습니다."));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("공지사항 삭제 실패: id={}", id, e);
return ResponseEntity.status(500).body(ApiResponse.error("공지사항 삭제 중 오류가 발생했습니다."));
}
}
}
@@ -0,0 +1,373 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.TableCategoryValueService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/table-categories")
@RequiredArgsConstructor
@Slf4j
public class TableCategoryValueController {
private final TableCategoryValueService service;
//
// Category Columns
//
/** GET /api/table-categories/all-columns */
@GetMapping("/all-columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAllCategoryColumns(
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getAllCategoryColumns(params)));
} catch (Exception e) {
log.error("전체 카테고리 컬럼 조회 실패", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("전체 카테고리 컬럼 조회 중 오류가 발생했습니다"));
}
}
/** GET /api/table-categories/{tableName}/columns */
@GetMapping("/{tableName}/columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCategoryColumns(
@PathVariable String tableName,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getCategoryColumns(params)));
} catch (Exception e) {
log.error("카테고리 컬럼 조회 실패: tableName={}", tableName, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("카테고리 컬럼 조회 중 오류가 발생했습니다"));
}
}
//
// Category Values Read
//
/** GET /api/table-categories/{tableName}/{columnName}/values */
@GetMapping("/{tableName}/{columnName}/values")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCategoryValues(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestParam(required = false) String menuObjid,
@RequestParam(required = false, defaultValue = "false") boolean includeInactive,
@RequestParam(required = false) String filterCompanyCode,
@RequestAttribute("companyCode") String companyCode) {
try {
// SUPER_ADMIN 특정 회사 기준 필터링 요청 해당 companyCode 사용
String effectiveCompanyCode = ("*".equals(companyCode) && filterCompanyCode != null)
? filterCompanyCode : companyCode;
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("columnName", columnName);
params.put("companyCode", effectiveCompanyCode);
params.put("includeInactive", includeInactive);
if (menuObjid != null) params.put("menuObjid", Long.parseLong(menuObjid));
return ResponseEntity.ok(ApiResponse.success(service.getCategoryValues(params)));
} catch (Exception e) {
log.error("카테고리 값 조회 실패: tableName={}, columnName={}", tableName, columnName, e);
return ResponseEntity.status(500)
.body(ApiResponse.error("카테고리 값 조회 중 오류가 발생했습니다"));
}
}
//
// Category Values Write
//
/** POST /api/table-categories/values */
@PostMapping("/values")
public ResponseEntity<ApiResponse<Map<String, Object>>> addCategoryValue(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
if (body.get("menuObjid") == null) {
return ResponseEntity.status(400).body(ApiResponse.error("menuObjid는 필수입니다"));
}
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(service.addCategoryValue(body)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(500).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "카테고리 값 추가 중 오류가 발생했습니다"));
} catch (Exception e) {
log.error("카테고리 값 추가 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("카테고리 값 추가 중 오류가 발생했습니다"));
}
}
/** PUT /api/table-categories/values/{valueId} */
@PutMapping("/values/{valueId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateCategoryValue(
@PathVariable Long valueId,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
body.put("valueId", valueId);
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
return ResponseEntity.ok(ApiResponse.success(service.updateCategoryValue(body)));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(500).body(ApiResponse.error("카테고리 값 수정 중 오류가 발생했습니다"));
} catch (Exception e) {
log.error("카테고리 값 수정 실패: valueId={}", valueId, e);
return ResponseEntity.status(500).body(ApiResponse.error("카테고리 값 수정 중 오류가 발생했습니다"));
}
}
/** DELETE /api/table-categories/values/{valueId} */
@DeleteMapping("/values/{valueId}")
public ResponseEntity<ApiResponse<Void>> deleteCategoryValue(
@PathVariable Long valueId,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
Map<String, Object> params = new HashMap<>();
params.put("valueId", valueId);
params.put("companyCode", companyCode);
params.put("userId", userId);
try {
service.deleteCategoryValue(params);
return ResponseEntity.ok(ApiResponse.success(null, "카테고리 값이 삭제되었습니다"));
} catch (IllegalArgumentException e) {
// 사용 중인 경우 400
if (e.getMessage() != null && e.getMessage().contains("삭제할 수 없습니다")) {
return ResponseEntity.status(400).body(ApiResponse.error(e.getMessage()));
}
return ResponseEntity.status(500).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "카테고리 값 삭제 중 오류가 발생했습니다"));
} catch (Exception e) {
log.error("카테고리 값 삭제 실패: valueId={}", valueId, e);
return ResponseEntity.status(500).body(ApiResponse.error("카테고리 값 삭제 중 오류가 발생했습니다"));
}
}
/** POST /api/table-categories/values/bulk-delete */
@PostMapping("/values/bulk-delete")
public ResponseEntity<ApiResponse<Void>> bulkDeleteCategoryValues(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
Object rawIds = body.get("valueIds");
if (!(rawIds instanceof List) || ((List<?>) rawIds).isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error("삭제할 값 ID 목록이 필요합니다"));
}
body.put("companyCode", companyCode);
body.put("userId", userId);
try {
service.bulkDeleteCategoryValues(body);
int count = ((List<?>) rawIds).size();
return ResponseEntity.ok(
ApiResponse.success(null, count + "개의 카테고리 값이 삭제되었습니다"));
} catch (Exception e) {
log.error("카테고리 값 일괄 삭제 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("카테고리 값 일괄 삭제 중 오류가 발생했습니다"));
}
}
/** POST /api/table-categories/values/reorder */
@PostMapping("/values/reorder")
public ResponseEntity<ApiResponse<Void>> reorderCategoryValues(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
Object rawIds = body.get("orderedValueIds");
if (!(rawIds instanceof List) || ((List<?>) rawIds).isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error("순서 정보가 필요합니다"));
}
body.put("companyCode", companyCode);
try {
service.reorderCategoryValues(body);
return ResponseEntity.ok(ApiResponse.success(null, "카테고리 값 순서가 변경되었습니다"));
} catch (Exception e) {
log.error("카테고리 값 순서 변경 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("카테고리 값 순서 변경 중 오류가 발생했습니다"));
}
}
//
// Labels by Codes
//
/** POST /api/table-categories/labels-by-codes */
@PostMapping("/labels-by-codes")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCategoryLabelsByCodes(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
Object rawCodes = body.get("valueCodes");
if (!(rawCodes instanceof List) || ((List<?>) rawCodes).isEmpty()) {
return ResponseEntity.ok(ApiResponse.success(new java.util.LinkedHashMap<>()));
}
body.put("companyCode", companyCode);
try {
return ResponseEntity.ok(ApiResponse.success(service.getCategoryLabelsByCodes(body)));
} catch (Exception e) {
log.error("카테고리 라벨 조회 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("카테고리 라벨 조회 중 오류가 발생했습니다"));
}
}
//
// Second-Level Menus (NOTE: 리터럴 경로이므로 variable 경로보다 우선)
//
/** GET /api/table-categories/second-level-menus */
@GetMapping("/second-level-menus")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getSecondLevelMenus(
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getSecondLevelMenus(params)));
} catch (Exception e) {
log.error("2레벨 메뉴 목록 조회 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("2레벨 메뉴 목록 조회 중 오류가 발생했습니다"));
}
}
//
// Column Mapping
//
/** GET /api/table-categories/column-mapping/{tableName}/{menuObjid} */
@GetMapping("/column-mapping/{tableName}/{menuObjid}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getColumnMapping(
@PathVariable String tableName,
@PathVariable Long menuObjid,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("menuObjid", menuObjid);
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getColumnMapping(params)));
} catch (Exception e) {
log.error("컬럼 매핑 조회 실패: tableName={}, menuObjid={}", tableName, menuObjid, e);
return ResponseEntity.status(500).body(ApiResponse.error("컬럼 매핑 조회 중 오류가 발생했습니다"));
}
}
/** GET /api/table-categories/logical-columns/{tableName}/{menuObjid} */
@GetMapping("/logical-columns/{tableName}/{menuObjid}")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getLogicalColumns(
@PathVariable String tableName,
@PathVariable Long menuObjid,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("menuObjid", menuObjid);
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(service.getLogicalColumns(params)));
} catch (Exception e) {
log.error("논리적 컬럼 목록 조회 실패: tableName={}, menuObjid={}", tableName, menuObjid, e);
return ResponseEntity.status(500).body(ApiResponse.error("논리적 컬럼 목록 조회 중 오류가 발생했습니다"));
}
}
/** POST /api/table-categories/column-mapping */
@PostMapping("/column-mapping")
public ResponseEntity<ApiResponse<Map<String, Object>>> createColumnMapping(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId) {
String tableName = (String) body.get("tableName");
String logicalColumnName = (String) body.get("logicalColumnName");
String physicalColumnName = (String) body.get("physicalColumnName");
Object menuObjid = body.get("menuObjid");
if (tableName == null || logicalColumnName == null
|| physicalColumnName == null || menuObjid == null) {
return ResponseEntity.status(400).body(ApiResponse.error(
"tableName, logicalColumnName, physicalColumnName, menuObjid는 필수입니다"));
}
body.put("companyCode", companyCode);
body.put("userId", userId);
// menuObjid를 Long으로 보장
body.put("menuObjid", toLong(menuObjid));
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(service.createColumnMapping(body), "컬럼 매핑이 생성되었습니다"));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(500).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "컬럼 매핑 생성 중 오류가 발생했습니다"));
} catch (Exception e) {
log.error("컬럼 매핑 생성 실패", e);
return ResponseEntity.status(500).body(ApiResponse.error("컬럼 매핑 생성 중 오류가 발생했습니다"));
}
}
/**
* DELETE /api/table-categories/column-mapping/{tableName}/{columnName}/all
* NOTE: 3-segment 경로이므로 /{mappingId} 1-segment 경로보다 Spring이 우선 매핑.
*/
@DeleteMapping("/column-mapping/{tableName}/{columnName}/all")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteColumnMappingsByColumn(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestAttribute("companyCode") String companyCode) {
try {
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("columnName", columnName);
params.put("companyCode", companyCode);
int deleted = service.deleteColumnMappingsByColumn(params);
Map<String, Object> data = new HashMap<>();
data.put("deletedCount", deleted);
return ResponseEntity.ok(ApiResponse.success(data,
deleted + "개의 컬럼 매핑이 삭제되었습니다"));
} catch (Exception e) {
log.error("테이블+컬럼 기준 매핑 삭제 실패: tableName={}, columnName={}", tableName, columnName, e);
return ResponseEntity.status(500).body(ApiResponse.error("컬럼 매핑 삭제 중 오류가 발생했습니다"));
}
}
/** DELETE /api/table-categories/column-mapping/{mappingId} */
@DeleteMapping("/column-mapping/{mappingId}")
public ResponseEntity<ApiResponse<Void>> deleteColumnMapping(
@PathVariable Long mappingId,
@RequestAttribute("companyCode") String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("mappingId", mappingId);
params.put("companyCode", companyCode);
try {
service.deleteColumnMapping(params);
return ResponseEntity.ok(ApiResponse.success(null, "컬럼 매핑이 삭제되었습니다"));
} catch (IllegalArgumentException e) {
return ResponseEntity.status(500).body(ApiResponse.error(
e.getMessage() != null ? e.getMessage() : "컬럼 매핑 삭제 중 오류가 발생했습니다"));
} catch (Exception e) {
log.error("컬럼 매핑 삭제 실패: mappingId={}", mappingId, e);
return ResponseEntity.status(500).body(ApiResponse.error("컬럼 매핑 삭제 중 오류가 발생했습니다"));
}
}
// private util
private long toLong(Object val) {
if (val == null) return 0L;
if (val instanceof Number) return ((Number) val).longValue();
try { return Long.parseLong(val.toString()); } catch (NumberFormatException e) { return 0L; }
}
}
@@ -0,0 +1,144 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.TableHistoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 테이블 이력 조회 컨트롤러
* Node: tableHistoryRoutes.ts + tableHistoryController.ts
*/
@RestController
@RequestMapping("/api/table-history")
@RequiredArgsConstructor
@Slf4j
public class TableHistoryController {
private final TableHistoryService tableHistoryService;
/**
* GET /api/table-history/{tableName}/check
* 이력 테이블 존재 여부 확인
*/
@GetMapping("/{tableName}/check")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkHistoryTableExists(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName) {
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("companyCode", companyCode);
Map<String, Object> data = tableHistoryService.checkHistoryTableExists(params);
String message = (String) data.remove("_message");
return ResponseEntity.ok(ApiResponse.success(data, message));
}
/**
* GET /api/table-history/{tableName}/summary
* 테이블 전체 이력 요약 (operation_type별 집계)
*/
@GetMapping("/{tableName}/summary")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTableHistorySummary(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName) {
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("companyCode", companyCode);
try {
List<Map<String, Object>> data = tableHistoryService.getTableHistorySummary(params);
return ResponseEntity.ok(ApiResponse.success(data, "이력 요약 조회 성공"));
} catch (RuntimeException e) {
if ("TABLE_NOT_FOUND".equals(e.getMessage())) {
return ResponseEntity.status(404).body(ApiResponse.error("이력 테이블이 존재하지 않습니다."));
}
throw e;
}
}
/**
* GET /api/table-history/{tableName}/all
* 전체 테이블 이력 조회 (레코드 ID 없이)
*/
@GetMapping("/{tableName}/all")
public ResponseEntity<ApiResponse<Map<String, Object>>> getAllTableHistory(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@RequestParam Map<String, Object> queryParams) {
queryParams.put("tableName", tableName);
queryParams.put("companyCode", companyCode);
try {
Map<String, Object> data = tableHistoryService.getAllTableHistory(queryParams);
return ResponseEntity.ok(ApiResponse.success(data, "전체 테이블 이력 조회 성공"));
} catch (RuntimeException e) {
if ("TABLE_NOT_FOUND".equals(e.getMessage())) {
return ResponseEntity.status(404).body(ApiResponse.error("이력 테이블이 존재하지 않습니다."));
}
throw e;
}
}
/**
* GET /api/table-history/{tableName}/{recordId}/timeline
* 특정 레코드 변경 타임라인 조회 (동일 시간대 그룹화)
*/
@GetMapping("/{tableName}/{recordId}/timeline")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getRecordTimeline(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@PathVariable String recordId) {
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("recordId", recordId);
params.put("companyCode", companyCode);
try {
List<Map<String, Object>> data = tableHistoryService.getRecordTimeline(params);
return ResponseEntity.ok(ApiResponse.success(data, "타임라인 조회 성공"));
} catch (RuntimeException e) {
if ("TABLE_NOT_FOUND".equals(e.getMessage())) {
return ResponseEntity.status(404).body(ApiResponse.error("이력 테이블이 존재하지 않습니다."));
}
throw e;
}
}
/**
* GET /api/table-history/{tableName}/{recordId}
* 특정 레코드 변경 이력 상세 조회
*/
@GetMapping("/{tableName}/{recordId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getRecordHistory(
@RequestAttribute("companyCode") String companyCode,
@PathVariable String tableName,
@PathVariable String recordId,
@RequestParam Map<String, Object> queryParams) {
queryParams.put("tableName", tableName);
queryParams.put("recordId", recordId);
queryParams.put("companyCode", companyCode);
try {
Map<String, Object> data = tableHistoryService.getRecordHistory(queryParams);
return ResponseEntity.ok(ApiResponse.success(data, "이력 조회 성공"));
} catch (RuntimeException e) {
if ("TABLE_NOT_FOUND".equals(e.getMessage())) {
return ResponseEntity.status(404).body(ApiResponse.error(
"이력 테이블이 존재하지 않습니다. 테이블 타입 관리에서 이력 관리를 활성화해주세요."));
}
throw e;
}
}
}
@@ -0,0 +1,578 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.TableManagementService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/table-management")
@RequiredArgsConstructor
@Slf4j
public class TableManagementController {
private final TableManagementService tableManagementService;
//
// 테이블 목록
//
/** GET /api/table-management/tables */
@GetMapping("/tables")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTableList() {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getTableList(), "테이블 목록을 성공적으로 조회했습니다."));
}
//
// 엔티티 관계 (literal segment이 path variable보다 우선)
//
/** GET /api/table-management/tables/entity-relations */
@GetMapping("/tables/entity-relations")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTableEntityRelations(
@RequestParam String leftTable,
@RequestParam String rightTable,
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getTableEntityRelations(leftTable, rightTable, companyCode)));
}
//
// 컬럼 목록
//
/** GET /api/table-management/tables/:tableName/columns */
@GetMapping("/tables/{tableName}/columns")
public ResponseEntity<ApiResponse<Map<String, Object>>> getColumnList(
@PathVariable String tableName,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "50") int size,
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getColumnList(tableName, page, size, companyCode),
"컬럼 목록을 성공적으로 조회했습니다."));
}
//
// 테이블 라벨
//
/** GET /api/table-management/tables/:tableName/labels */
@GetMapping("/tables/{tableName}/labels")
public ResponseEntity<ApiResponse<Object>> getTableLabels(@PathVariable String tableName) {
Map<String, Object> labels = tableManagementService.getTableLabels(tableName);
Object data = labels != null ? labels : Map.of();
return ResponseEntity.ok(ApiResponse.success(data, "테이블 라벨 정보를 조회했습니다."));
}
/** PUT /api/table-management/tables/:tableName/label */
@PutMapping("/tables/{tableName}/label")
public ResponseEntity<ApiResponse<Void>> updateTableLabel(
@PathVariable String tableName,
@RequestBody Map<String, Object> body) {
String displayName = (String) body.get("displayName");
String description = (String) body.get("description");
if (displayName == null || displayName.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("표시명이 필요합니다."));
}
tableManagementService.updateTableLabel(tableName, displayName, description);
return ResponseEntity.ok(ApiResponse.success(null, "테이블 라벨이 성공적으로 설정되었습니다."));
}
//
// 컬럼 설정
//
/** GET /api/table-management/tables/:tableName/columns/:columnName/labels */
@GetMapping("/tables/{tableName}/columns/{columnName}/labels")
public ResponseEntity<ApiResponse<Object>> getColumnLabels(
@PathVariable String tableName,
@PathVariable String columnName) {
Map<String, Object> labels = tableManagementService.getColumnLabels(tableName, columnName);
Object data = labels != null ? labels : Map.of();
return ResponseEntity.ok(ApiResponse.success(data, "컬럼 라벨 정보를 조회했습니다."));
}
/** POST /api/table-management/tables/:tableName/columns/:columnName/settings */
@PostMapping("/tables/{tableName}/columns/{columnName}/settings")
public ResponseEntity<ApiResponse<Void>> updateColumnSettingsPost(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestBody Map<String, Object> settings,
@RequestAttribute("companyCode") String companyCode) {
return doUpdateColumnSettings(tableName, columnName, settings, companyCode);
}
/** PUT /api/table-management/tables/:tableName/columns/:columnName */
@PutMapping("/tables/{tableName}/columns/{columnName}")
public ResponseEntity<ApiResponse<Void>> updateColumnSettingsPut(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestBody Map<String, Object> settings,
@RequestAttribute("companyCode") String companyCode) {
return doUpdateColumnSettings(tableName, columnName, settings, companyCode);
}
private ResponseEntity<ApiResponse<Void>> doUpdateColumnSettings(
String tableName, String columnName, Map<String, Object> settings, String companyCode) {
if (tableName == null || columnName == null) {
return ResponseEntity.status(400).body(ApiResponse.error("테이블명과 컬럼명이 필요합니다."));
}
if (settings == null) {
return ResponseEntity.status(400).body(ApiResponse.error("컬럼 설정 정보가 필요합니다."));
}
tableManagementService.updateColumnSettings(tableName, columnName, settings, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "컬럼 설정을 성공적으로 저장했습니다."));
}
/** POST /api/table-management/tables/:tableName/columns/settings (batch) */
@PostMapping("/tables/{tableName}/columns/settings")
public ResponseEntity<ApiResponse<Void>> updateAllColumnSettingsPost(
@PathVariable String tableName,
@RequestBody List<Map<String, Object>> columnSettings,
@RequestAttribute("companyCode") String companyCode) {
return doUpdateAllColumnSettings(tableName, columnSettings, companyCode);
}
/** PUT /api/table-management/tables/:tableName/columns/batch */
@PutMapping("/tables/{tableName}/columns/batch")
public ResponseEntity<ApiResponse<Void>> updateAllColumnSettingsBatch(
@PathVariable String tableName,
@RequestBody List<Map<String, Object>> columnSettings,
@RequestAttribute("companyCode") String companyCode) {
return doUpdateAllColumnSettings(tableName, columnSettings, companyCode);
}
private ResponseEntity<ApiResponse<Void>> doUpdateAllColumnSettings(
String tableName, List<Map<String, Object>> columnSettings, String companyCode) {
if (tableName == null) {
return ResponseEntity.status(400).body(ApiResponse.error("테이블명이 필요합니다."));
}
if (columnSettings == null || columnSettings.isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error("컬럼 설정 목록이 필요합니다."));
}
tableManagementService.updateAllColumnSettings(tableName, columnSettings, companyCode);
return ResponseEntity.ok(ApiResponse.success(null, "모든 컬럼 설정을 성공적으로 저장했습니다."));
}
/** PUT /api/table-management/tables/:tableName/columns/:columnName/web-type */
@PutMapping("/tables/{tableName}/columns/{columnName}/web-type")
public ResponseEntity<ApiResponse<Void>> updateColumnWebType(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestBody Map<String, Object> body) {
String webType = (String) body.get("webType");
if (webType == null || webType.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("웹 타입이 필요합니다."));
}
@SuppressWarnings("unchecked")
Map<String, Object> detailSettings = (Map<String, Object>) body.get("detailSettings");
tableManagementService.updateColumnWebType(tableName, columnName, webType, detailSettings);
return ResponseEntity.ok(ApiResponse.success(null, "컬럼 웹타입이 설정되었습니다."));
}
/** PUT /api/table-management/tables/:tableName/columns/:columnName/input-type */
@PutMapping("/tables/{tableName}/columns/{columnName}/input-type")
public ResponseEntity<ApiResponse<Void>> updateColumnInputType(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
String inputType = (String) body.get("inputType");
if (tableName == null || columnName == null || inputType == null || inputType.isBlank()) {
return ResponseEntity.status(400).body(ApiResponse.error("테이블명, 컬럼명, 입력 타입이 모두 필요합니다."));
}
@SuppressWarnings("unchecked")
Map<String, Object> detailSettings = (Map<String, Object>) body.get("detailSettings");
tableManagementService.updateColumnInputType(tableName, columnName, inputType, companyCode, detailSettings);
return ResponseEntity.ok(ApiResponse.success(null, "컬럼 입력 타입이 성공적으로 설정되었습니다."));
}
//
// 테이블 스키마 / 존재 여부 / 웹타입
//
/** GET /api/table-management/tables/:tableName/schema */
@GetMapping("/tables/{tableName}/schema")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getTableSchema(
@PathVariable String tableName) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getTableSchema(tableName),
"테이블 스키마 정보를 성공적으로 조회했습니다."));
}
/** GET /api/table-management/tables/:tableName/exists */
@GetMapping("/tables/{tableName}/exists")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkTableExists(
@PathVariable String tableName) {
boolean exists = tableManagementService.checkTableExists(tableName);
return ResponseEntity.ok(ApiResponse.success(Map.of("exists", exists), "테이블 존재 여부를 확인했습니다."));
}
/** GET /api/table-management/tables/:tableName/web-types */
@GetMapping("/tables/{tableName}/web-types")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getColumnWebTypes(
@PathVariable String tableName,
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getColumnInputTypes(tableName, companyCode),
"컬럼 입력타입 정보를 성공적으로 조회했습니다."));
}
//
// 제약조건 관리
//
/** GET /api/table-management/tables/:tableName/constraints */
@GetMapping("/tables/{tableName}/constraints")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTableConstraints(
@PathVariable String tableName) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getTableConstraints(tableName)));
}
/** PUT /api/table-management/tables/:tableName/primary-key */
@PutMapping("/tables/{tableName}/primary-key")
public ResponseEntity<ApiResponse<Void>> setTablePrimaryKey(
@PathVariable String tableName,
@RequestBody Map<String, Object> body) {
@SuppressWarnings("unchecked")
List<String> columns = (List<String>) body.get("columns");
if (tableName == null || columns == null || columns.isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error("테이블명과 PK 컬럼 배열이 필요합니다."));
}
tableManagementService.setTablePrimaryKey(tableName, columns);
return ResponseEntity.ok(ApiResponse.success(null,
"PK가 설정되었습니다: " + String.join(", ", columns)));
}
/** POST /api/table-management/tables/:tableName/indexes */
@PostMapping("/tables/{tableName}/indexes")
public ResponseEntity<ApiResponse<Void>> toggleTableIndex(
@PathVariable String tableName,
@RequestBody Map<String, Object> body) {
String columnName = (String) body.get("columnName");
String indexType = (String) body.get("indexType");
String action = (String) body.get("action");
if (tableName == null || columnName == null || indexType == null || action == null) {
return ResponseEntity.status(400).body(ApiResponse.error(
"tableName, columnName, indexType(index|unique), action(create|drop)이 필요합니다."));
}
try {
tableManagementService.toggleTableIndex(tableName, columnName, indexType, action);
String msg = "create".equals(action)
? "인덱스가 생성되었습니다."
: "인덱스가 삭제되었습니다.";
return ResponseEntity.ok(ApiResponse.success(null, msg));
} catch (Exception e) {
return ResponseEntity.status(500).body(ApiResponse.error(e.getMessage()));
}
}
/** PUT /api/table-management/tables/:tableName/columns/:columnName/nullable */
@PutMapping("/tables/{tableName}/columns/{columnName}/nullable")
public ResponseEntity<ApiResponse<Void>> toggleColumnNullable(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
Object nullableObj = body.get("nullable");
if (tableName == null || columnName == null || !(nullableObj instanceof Boolean)) {
return ResponseEntity.status(400).body(ApiResponse.error("tableName, columnName, nullable(boolean)이 필요합니다."));
}
boolean nullable = (Boolean) nullableObj;
tableManagementService.toggleColumnNullable(tableName, columnName, nullable, companyCode);
String msg = nullable ? columnName + " 컬럼의 NOT NULL 제약이 해제되었습니다."
: columnName + " 컬럼이 NOT NULL로 설정되었습니다.";
return ResponseEntity.ok(ApiResponse.success(null, msg));
}
/** PUT /api/table-management/tables/:tableName/columns/:columnName/unique */
@PutMapping("/tables/{tableName}/columns/{columnName}/unique")
public ResponseEntity<ApiResponse<Void>> toggleColumnUnique(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
Object uniqueObj = body.get("unique");
if (tableName == null || columnName == null || !(uniqueObj instanceof Boolean)) {
return ResponseEntity.status(400).body(ApiResponse.error("tableName, columnName, unique(boolean)이 필요합니다."));
}
boolean unique = (Boolean) uniqueObj;
tableManagementService.toggleColumnUnique(tableName, columnName, unique, companyCode);
String msg = unique ? columnName + " 컬럼이 UNIQUE로 설정되었습니다."
: columnName + " 컬럼의 UNIQUE 제약이 해제되었습니다.";
return ResponseEntity.ok(ApiResponse.success(null, msg));
}
//
// 데이터 CRUD
//
/** POST /api/table-management/tables/:tableName/data */
@PostMapping("/tables/{tableName}/data")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTableData(
@PathVariable String tableName,
@RequestBody Map<String, Object> options) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getTableData(tableName, options),
"테이블 데이터를 성공적으로 조회했습니다."));
}
/** POST /api/table-management/tables/:tableName/record (단일 레코드) */
@PostMapping("/tables/{tableName}/record")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTableRecord(
@PathVariable String tableName,
@RequestBody Map<String, Object> body) {
String filterColumn = (String) body.get("filterColumn");
Object filterValue = body.get("filterValue");
String displayColumn = (String) body.get("displayColumn");
if (filterColumn == null || filterValue == null) {
return ResponseEntity.status(400).body(ApiResponse.error(
"tableName, filterColumn, filterValue가 필요합니다."));
}
Map<String, Object> options = new java.util.HashMap<>();
options.put("page", 1);
options.put("size", 1);
options.put("search", Map.of(filterColumn, filterValue.toString()));
Map<String, Object> result = tableManagementService.getTableData(tableName, options);
@SuppressWarnings("unchecked")
List<Map<String, Object>> data = (List<Map<String, Object>>) result.get("data");
if (data == null || data.isEmpty()) {
return ResponseEntity.status(404).body(ApiResponse.error("데이터를 찾을 수 없습니다."));
}
Map<String, Object> record = data.get(0);
Object displayValue = (displayColumn != null && !displayColumn.equals("*"))
? record.get(displayColumn) : record;
return ResponseEntity.ok(ApiResponse.success(
Map.of("value", displayValue != null ? displayValue : "", "record", record),
"레코드를 성공적으로 조회했습니다."));
}
/** POST /api/table-management/tables/:tableName/add */
@PostMapping("/tables/{tableName}/add")
public ResponseEntity<ApiResponse<Map<String, Object>>> addTableData(
@PathVariable String tableName,
@RequestBody Map<String, Object> data,
@RequestAttribute("companyCode") String companyCode) {
if (data == null || data.isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error("추가할 데이터가 필요합니다."));
}
// 멀티테넌시: company_code 자동 추가
if (!data.containsKey("company_code") && tableManagementService.hasColumn(tableName, "company_code")) {
data.put("company_code", companyCode);
}
// 소프트 제약조건 검증
List<String> notNullViolations = tableManagementService.validateNotNullConstraints(tableName, data, companyCode);
if (!notNullViolations.isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error(
"필수 항목이 비어있습니다: " + String.join(", ", notNullViolations)));
}
List<String> uniqueViolations = tableManagementService.validateUniqueConstraints(tableName, data, companyCode, null);
if (!uniqueViolations.isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error(
"중복된 값이 존재합니다: " + String.join(", ", uniqueViolations)));
}
Map<String, Object> result = tableManagementService.addTableData(tableName, data);
return ResponseEntity.status(201).body(ApiResponse.success(
Map.of("id", result.getOrDefault("insertedId", "")),
"테이블 데이터를 성공적으로 추가했습니다."));
}
/** PUT /api/table-management/tables/:tableName/edit */
@PutMapping("/tables/{tableName}/edit")
public ResponseEntity<ApiResponse<Void>> editTableData(
@PathVariable String tableName,
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
@SuppressWarnings("unchecked")
Map<String, Object> originalData = (Map<String, Object>) body.get("originalData");
@SuppressWarnings("unchecked")
Map<String, Object> updatedData = (Map<String, Object>) body.get("updatedData");
if (originalData == null || updatedData == null) {
return ResponseEntity.status(400).body(ApiResponse.error("원본 데이터와 수정할 데이터가 모두 필요합니다."));
}
if (updatedData.isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error("수정할 데이터가 없습니다."));
}
// 소프트 제약조건 검증
List<String> notNullViolations = tableManagementService.validateNotNullConstraints(tableName, updatedData, companyCode);
if (!notNullViolations.isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error(
"필수 항목이 비어있습니다: " + String.join(", ", notNullViolations)));
}
String excludeId = originalData.get("id") != null ? originalData.get("id").toString() : null;
List<String> uniqueViolations = tableManagementService.validateUniqueConstraints(tableName, updatedData, companyCode, excludeId);
if (!uniqueViolations.isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error(
"중복된 값이 존재합니다: " + String.join(", ", uniqueViolations)));
}
tableManagementService.editTableData(tableName, originalData, updatedData);
return ResponseEntity.ok(ApiResponse.success(null, "테이블 데이터를 성공적으로 수정했습니다."));
}
/** DELETE /api/table-management/tables/:tableName/delete */
@DeleteMapping("/tables/{tableName}/delete")
public ResponseEntity<ApiResponse<Void>> deleteTableData(
@PathVariable String tableName,
@RequestBody Object body) {
List<Map<String, Object>> dataList;
if (body instanceof List) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> cast = (List<Map<String, Object>>) body;
dataList = cast;
} else {
return ResponseEntity.status(400).body(ApiResponse.error("삭제할 데이터가 필요합니다."));
}
if (dataList.isEmpty()) {
return ResponseEntity.status(400).body(ApiResponse.error("삭제할 데이터가 필요합니다."));
}
tableManagementService.deleteTableData(tableName, dataList);
return ResponseEntity.ok(ApiResponse.success(null, "테이블 데이터를 성공적으로 삭제했습니다."));
}
//
// 로그 관리
//
/** POST /api/table-management/tables/:tableName/log */
@PostMapping("/tables/{tableName}/log")
public ResponseEntity<ApiResponse<Void>> createLogTable(
@PathVariable String tableName,
@RequestBody Map<String, Object> body) {
@SuppressWarnings("unchecked")
List<String> logColumns = (List<String>) body.get("logColumns");
boolean isActive = Boolean.TRUE.equals(body.get("isActive"));
tableManagementService.createLogTable(tableName, logColumns, isActive);
return ResponseEntity.ok(ApiResponse.success(null, "로그 테이블이 생성되었습니다."));
}
/** GET /api/table-management/tables/:tableName/log/config */
@GetMapping("/tables/{tableName}/log/config")
public ResponseEntity<ApiResponse<Object>> getLogConfig(@PathVariable String tableName) {
Map<String, Object> config = tableManagementService.getLogConfig(tableName);
Object data = config != null ? config : Map.of();
return ResponseEntity.ok(ApiResponse.success(data));
}
/** GET /api/table-management/tables/:tableName/log */
@GetMapping("/tables/{tableName}/log")
public ResponseEntity<ApiResponse<Map<String, Object>>> getLogData(
@PathVariable String tableName,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "50") int size) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getLogData(tableName, page, size)));
}
/** POST /api/table-management/tables/:tableName/log/toggle */
@PostMapping("/tables/{tableName}/log/toggle")
public ResponseEntity<ApiResponse<Void>> toggleLogTable(
@PathVariable String tableName,
@RequestBody Map<String, Object> body) {
boolean isActive = Boolean.TRUE.equals(body.get("isActive"));
tableManagementService.toggleLogTable(tableName, isActive);
return ResponseEntity.ok(ApiResponse.success(null,
isActive ? "로그가 활성화되었습니다." : "로그가 비활성화되었습니다."));
}
//
// 카테고리 / 채번 컬럼
//
/** GET /api/table-management/category-columns */
@GetMapping("/category-columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCategoryColumnsByCompany(
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getCategoryColumnsByCompany(companyCode)));
}
/** GET /api/table-management/numbering-columns */
@GetMapping("/numbering-columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getNumberingColumnsByCompany(
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getNumberingColumnsByCompany(companyCode)));
}
/** GET /api/table-management/menu/:menuObjid/category-columns */
@GetMapping("/menu/{menuObjid}/category-columns")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getCategoryColumnsByMenu(
@PathVariable String menuObjid,
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getCategoryColumnsByMenu(companyCode, menuObjid)));
}
//
// 현재 테이블을 참조하는 테이블
//
/** GET /api/table-management/columns/:tableName/referenced-by */
@GetMapping("/columns/{tableName}/referenced-by")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getReferencedByTables(
@PathVariable String tableName,
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.getReferencedByTables(tableName, companyCode)));
}
//
// 다중 테이블 저장 / 엑셀 검증
//
/** POST /api/table-management/multi-table-save */
@PostMapping("/multi-table-save")
public ResponseEntity<ApiResponse<Map<String, Object>>> multiTableSave(
@RequestBody Map<String, Object> payload,
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.multiTableSave(payload, companyCode),
"다중 테이블 저장이 완료되었습니다."));
}
/** POST /api/table-management/validate-excel */
@PostMapping("/validate-excel")
public ResponseEntity<ApiResponse<Map<String, Object>>> validateExcelData(
@RequestBody Map<String, Object> body,
@RequestAttribute("companyCode") String companyCode) {
String tableName = (String) body.get("tableName");
@SuppressWarnings("unchecked")
List<Map<String, Object>> rows = (List<Map<String, Object>>) body.get("rows");
if (tableName == null || rows == null) {
return ResponseEntity.status(400).body(ApiResponse.error("tableName과 rows가 필요합니다."));
}
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.validateExcelData(tableName, rows, companyCode)));
}
//
// DB 헬스체크
//
/** GET /api/table-management/health */
@GetMapping("/health")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkDatabaseConnection() {
return ResponseEntity.ok(ApiResponse.success(
tableManagementService.checkDatabaseConnection(), "데이터베이스 연결 상태를 확인했습니다."));
}
}
@@ -0,0 +1,66 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.TaxInvoiceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/tax-invoice")
@RequiredArgsConstructor
@Slf4j
public class TaxInvoiceController {
private final TaxInvoiceService taxInvoiceService;
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTaxInvoiceList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(taxInvoiceService.getTaxInvoiceList(params)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTaxInvoiceInfo(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(taxInvoiceService.getTaxInvoiceInfo(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertTaxInvoice(
@RequestAttribute("companyCode") String companyCode,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
return ResponseEntity.ok(ApiResponse.success(taxInvoiceService.insertTaxInvoice(body)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateTaxInvoice(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id,
@RequestBody Map<String, Object> body) {
body.put("companyCode", companyCode);
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(taxInvoiceService.updateTaxInvoice(body)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteTaxInvoice(
@RequestAttribute("companyCode") String companyCode,
@PathVariable Long id) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("id", id);
return ResponseEntity.ok(ApiResponse.success(taxInvoiceService.deleteTaxInvoice(params)));
}
}
@@ -0,0 +1,235 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.TemplateStandardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@Slf4j
@RestController
@RequestMapping("/api/admin/template-standards")
@RequiredArgsConstructor
public class TemplateStandardController {
private final TemplateStandardService templateStandardService;
/**
* GET /api/admin/template-standards
* 템플릿 목록 조회 (페이지네이션)
*/
@GetMapping
public ResponseEntity<Map<String, Object>> getTemplateStandardList(
@RequestAttribute("companyCode") String companyCode,
@RequestParam Map<String, Object> params) {
params.putIfAbsent("companyCode", companyCode);
Map<String, Object> result = templateStandardService.getTemplateStandardList(params);
int total = ((Number) result.get("total")).intValue();
int page = ((Number) result.get("page")).intValue();
int limit = ((Number) result.get("limit")).intValue();
Map<String, Object> pagination = new LinkedHashMap<>();
pagination.put("total", total);
pagination.put("page", page);
pagination.put("limit", limit);
pagination.put("totalPages", (int) Math.ceil((double) total / limit));
Map<String, Object> response = new LinkedHashMap<>();
response.put("success", true);
response.put("data", result.get("templates"));
response.put("pagination", pagination);
return ResponseEntity.ok(response);
}
/**
* GET /api/admin/template-standards/categories
* 템플릿 카테고리 목록 조회
*/
@GetMapping("/categories")
public ResponseEntity<ApiResponse<List<String>>> getTemplateStandardCategoryList(
@RequestAttribute("companyCode") String companyCode) {
List<String> categories = templateStandardService.getTemplateStandardCategoryList(companyCode);
return ResponseEntity.ok(ApiResponse.success(categories));
}
/**
* PUT /api/admin/template-standards/sort-order/bulk
* 템플릿 정렬 순서 일괄 업데이트
*/
@PutMapping("/sort-order/bulk")
public ResponseEntity<ApiResponse<Void>> updateTemplateStandardSortOrder(
@RequestBody Map<String, Object> body) {
Object raw = body.get("templates");
if (!(raw instanceof List)) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("templates는 배열이어야 합니다."));
}
@SuppressWarnings("unchecked")
List<Map<String, Object>> templates = (List<Map<String, Object>>) raw;
templateStandardService.updateTemplateStandardSortOrder(templates);
return ResponseEntity.ok(ApiResponse.success(null, "템플릿 정렬 순서가 성공적으로 업데이트되었습니다."));
}
/**
* POST /api/admin/template-standards/import
* 템플릿 가져오기 (JSON에서)
*/
@PostMapping("/import")
public ResponseEntity<ApiResponse<Map<String, Object>>> importTemplateStandard(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
if (body.get("layout_config") == null) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("유효한 템플릿 데이터가 아닙니다."));
}
body.put("company_code", companyCode);
body.put("created_by", userId);
body.put("updated_by", userId);
Map<String, Object> created = templateStandardService.insertTemplateStandard(body);
return ResponseEntity.status(201).body(ApiResponse.success(created, "템플릿이 성공적으로 가져왔습니다."));
}
/**
* GET /api/admin/template-standards/{templateCode}
* 템플릿 상세 조회
*/
@GetMapping("/{templateCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTemplateStandardInfo(
@PathVariable String templateCode) {
Map<String, Object> template = templateStandardService.getTemplateStandardInfo(templateCode);
if (template == null) {
return ResponseEntity.status(404).body(ApiResponse.error("템플릿을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(template));
}
/**
* GET /api/admin/template-standards/{templateCode}/export
* 템플릿 내보내기 (메타데이터 제외 서브셋)
*/
@GetMapping("/{templateCode}/export")
public ResponseEntity<ApiResponse<Map<String, Object>>> exportTemplateStandard(
@PathVariable String templateCode) {
Map<String, Object> template = templateStandardService.getTemplateStandardInfo(templateCode);
if (template == null) {
return ResponseEntity.status(404).body(ApiResponse.error("템플릿을 찾을 수 없습니다."));
}
// DB 결과는 camelCase (map-underscore-to-camel-case: true)
// 응답은 Node.js 호환을 위해 snake_case 사용
Map<String, Object> exportData = new LinkedHashMap<>();
exportData.put("template_code", template.get("templateCode"));
exportData.put("template_name", template.get("templateName"));
exportData.put("template_name_eng", template.get("templateNameEng"));
exportData.put("description", template.get("description"));
exportData.put("category", template.get("category"));
exportData.put("icon_name", template.get("iconName"));
exportData.put("default_size", template.get("defaultSize"));
exportData.put("layout_config", template.get("layoutConfig"));
return ResponseEntity.ok(ApiResponse.success(exportData));
}
/**
* POST /api/admin/template-standards
* 템플릿 생성
*/
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertTemplateStandard(
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
if (body.get("template_code") == null || body.get("template_name") == null
|| body.get("category") == null || body.get("layout_config") == null) {
return ResponseEntity.badRequest().body(ApiResponse.error(
"필수 필드가 누락되었습니다. (template_code, template_name, category, layout_config)"));
}
body.put("company_code", companyCode);
body.put("created_by", userId);
body.put("updated_by", userId);
Map<String, Object> created = templateStandardService.insertTemplateStandard(body);
return ResponseEntity.status(201).body(ApiResponse.success(created, "템플릿이 성공적으로 생성되었습니다."));
}
/**
* PUT /api/admin/template-standards/{templateCode}
* 템플릿 수정
*/
@PutMapping("/{templateCode}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateTemplateStandard(
@PathVariable String templateCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
body.put("updated_by", userId);
Map<String, Object> updated = templateStandardService.updateTemplateStandard(templateCode, body);
if (updated == null) {
return ResponseEntity.status(404).body(ApiResponse.error("템플릿을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(updated, "템플릿이 성공적으로 수정되었습니다."));
}
/**
* DELETE /api/admin/template-standards/{templateCode}
* 템플릿 삭제
*/
@DeleteMapping("/{templateCode}")
public ResponseEntity<ApiResponse<Void>> deleteTemplateStandard(
@PathVariable String templateCode) {
boolean deleted = templateStandardService.deleteTemplateStandard(templateCode);
if (!deleted) {
return ResponseEntity.status(404).body(ApiResponse.error("템플릿을 찾을 수 없습니다."));
}
return ResponseEntity.ok(ApiResponse.success(null, "템플릿이 성공적으로 삭제되었습니다."));
}
/**
* POST /api/admin/template-standards/{templateCode}/duplicate
* 템플릿 복제
*/
@PostMapping("/{templateCode}/duplicate")
public ResponseEntity<ApiResponse<Map<String, Object>>> duplicateTemplateStandard(
@PathVariable String templateCode,
@RequestAttribute("companyCode") String companyCode,
@RequestAttribute("userId") String userId,
@RequestBody Map<String, Object> body) {
String newTemplateCode = (String) body.get("new_template_code");
String newTemplateName = (String) body.get("new_template_name");
if (newTemplateCode == null || newTemplateName == null) {
return ResponseEntity.badRequest().body(ApiResponse.error("필수 필드가 누락되었습니다."));
}
Map<String, Object> params = new HashMap<>();
params.put("originalCode", templateCode);
params.put("newCode", newTemplateCode);
params.put("newName", newTemplateName);
params.put("company_code", companyCode);
params.put("created_by", userId);
Map<String, Object> duplicated = templateStandardService.duplicateTemplateStandard(params);
return ResponseEntity.status(201).body(ApiResponse.success(duplicated, "템플릿이 성공적으로 복제되었습니다."));
}
}
@@ -0,0 +1,61 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.TestButtonDataflowService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* Test Button Dataflow Controller 추가 엔드포인트
*
* testButtonDataflowRoutes.ts 에서 buttonDataflowRoutes 없는
* 가지 추가 엔드포인트만 담당한다:
*
* GET /api/test-button-dataflow/relationships/all 버튼 제어용 전체 관계 목록
* GET /api/test-button-dataflow/test-status 테스트 모드 상태 확인
*
* 나머지 엔드포인트(config, diagrams, execute-*, job-status )
* ButtonDataflowController 동일 기본 경로에서 처리한다.
* Spring MVC @RequestMapping 같아도 개별 @GetMapping 경로가 다르면 정상 동작한다.
*/
@RestController
@RequestMapping("/api/test-button-dataflow")
@RequiredArgsConstructor
@Slf4j
public class TestButtonDataflowController {
private final TestButtonDataflowService service;
//
// GET /relationships/all
// Node.js: router.get("/relationships/all", getAllRelationships)
//
@GetMapping("/relationships/all")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getAllRelationships(
@RequestAttribute("companyCode") String companyCode) {
try {
return ResponseEntity.ok(ApiResponse.success(service.getAllRelationships(companyCode)));
} catch (Exception e) {
log.error("전체 관계 목록 조회 오류", e);
return ResponseEntity.status(500)
.body(ApiResponse.error("전체 관계 목록 조회 중 오류가 발생했습니다."));
}
}
//
// GET /test-status
// Node.js: router.get("/test-status", (req, res) => res.json({...}))
//
@GetMapping("/test-status")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTestStatus(
@RequestAttribute("companyCode") String companyCode) {
return ResponseEntity.ok(ApiResponse.success(service.getTestStatus(companyCode)));
}
}
@@ -0,0 +1,71 @@
package com.erp.controller;
import com.erp.dto.ApiResponse;
import com.erp.service.TodoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/todo")
@RequiredArgsConstructor
@Slf4j
public class TodoController {
private final TodoService todoService;
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getTodoList(
@RequestParam Map<String, Object> params) {
return ResponseEntity.ok(ApiResponse.success(todoService.getTodoList(params)));
}
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> insertTodo(
@RequestBody Map<String, Object> body) {
Map<String, Object> result = todoService.insertTodo(body);
return ResponseEntity.status(201).body(ApiResponse.success(result, "To-Do가 생성되었습니다."));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateTodo(
@PathVariable String id,
@RequestBody Map<String, Object> body) {
body.put("id", id);
return ResponseEntity.ok(ApiResponse.success(todoService.updateTodo(body), "To-Do가 수정되었습니다."));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Map<String, Object>>> deleteTodo(
@PathVariable String id) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
todoService.deleteTodo(params);
return ResponseEntity.ok(ApiResponse.success(null, "To-Do가 삭제되었습니다."));
}
@PostMapping("/reorder")
public ResponseEntity<ApiResponse<Map<String, Object>>> reorderTodos(
@RequestBody Map<String, Object> body) {
Object todoIdsObj = body.get("todoIds");
if (!(todoIdsObj instanceof List)) {
return ResponseEntity.status(400).body(
ApiResponse.error("todoIds는 배열이어야 합니다."));
}
List<String> todoIds = new ArrayList<>();
for (Object id : (List<?>) todoIdsObj) {
todoIds.add(String.valueOf(id));
}
todoService.reorderTodos(todoIds);
return ResponseEntity.ok(ApiResponse.success(null, "To-Do 순서가 변경되었습니다."));
}
// 이전 호환
@GetMapping("/list")
public ResponseEntity<ApiResponse<Map<String, Object>>> getTodoListLegacy(
@RequestParam Map<String, Object> params) {
return ResponseEntity.ok(ApiResponse.success(todoService.getTodoList(params)));
}
}

Some files were not shown because too many files have changed in this diff Show More