21 Commits

Author SHA1 Message Date
jade
8b4d4da0b9 Merge branch 'main' of https://git.devjun.net/kimhyungsik/ax_hub_mcp_tool into DEV-SRTEST 2026-07-29 17:02:21 +09:00
jade
c551b15378 테스트 푸쉬 2026-07-29 17:01:05 +09:00
juheelee
b448e68f45 Merge branch 'main' of https://git.devjun.net/kimhyungsik/ax_hub_mcp_tool
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 2m49s
2026-07-29 16:50:56 +09:00
juheelee
b68fbb9726 feat: 메타 통합 코드 조회 2026-07-29 16:48:28 +09:00
c6a2f82455 push TEST
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 2m16s
2026-07-29 16:08:45 +09:00
juheelee
2f5dad05df feat: 메타 통합 코드 조회 2026-07-29 15:34:01 +09:00
jade
b0bf4a9c68 fix: use rsync to clean stale files during host volume sync
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 2m1s
2026-07-29 13:25:10 +09:00
jade
752aae4dde fix: run gradle natively in runner to avoid isolated mount issue
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 1m50s
2026-07-29 13:22:03 +09:00
jade
5cb37cfda6 fix: mount correct app path for gradle build
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 4s
2026-07-29 13:16:31 +09:00
jade
2a4b3e68e9 fix: allow gradle build libs to be copied into docker container
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 2m3s
2026-07-29 13:03:11 +09:00
jade
ca1082f1c2 fix: rollback register to false as per user request
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 31s
2026-07-29 12:58:22 +09:00
jade
7b1d3131e6 fix: set register true for tools to fix catalog display
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 33s
2026-07-29 12:55:06 +09:00
jade
139ddbc6ca fix: optimize dockerfile to use external gradle build
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 37s
2026-07-29 12:54:02 +09:00
jade
870ebb723f fix: hide markdown emphasis markers in chat responses
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m22s
2026-07-29 11:35:22 +09:00
jade
a08a7d25df refactor: move team member tool to smp category
Some checks failed
Deploy to OCIWP / deploy (push) Has been cancelled
2026-07-29 11:30:52 +09:00
jade
60acc5df81 refactor: rename solgit tool to sol
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m23s
2026-07-29 11:23:49 +09:00
jade
f5be5b2514 refactor: remove unused team member legacy types
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m32s
2026-07-29 11:14:44 +09:00
jade
aae4898d18 feat: invoke Solgit MCI before returning dummy response
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m26s
2026-07-29 11:02:10 +09:00
jade
06b724459e feat: add solgitReqList and get_mcp_tool_team_members tools
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m31s
2026-07-29 10:26:32 +09:00
jade
4133b63595 불필요 한 파일 삭제
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m19s
2026-07-28 14:39:50 +09:00
jade
1e18d2bc47 feat: support nested MCP input schema validation
Some checks failed
Deploy to OCIWP / deploy (push) Has been cancelled
2026-07-28 14:39:31 +09:00
33 changed files with 835 additions and 168 deletions

View File

@@ -1,8 +1,7 @@
.git
.git
.gradle
.idea
build/
*/build/
/build/
target/
*/target/
bin/

View File

@@ -12,13 +12,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Prepare Environment (Install Node.js & Git)
- name: Prepare Environment (Install Node.js & Git & Java 21)
run: |
if command -v apt-get &> /dev/null; then
apt-get update
apt-get install -y nodejs git
apt-get install -y nodejs git openjdk-21-jdk rsync
elif command -v apk &> /dev/null; then
apk add --no-cache nodejs git
apk add --no-cache nodejs git openjdk21 rsync
fi
- name: Checkout Code
@@ -26,21 +26,22 @@ jobs:
- name: Sync Code to Host Volume
run: |
echo "Copying latest code to /app (Host Volume)..."
cp -a . /app/
echo "Copying latest code to /app (Host Volume) and removing stale files..."
if command -v rsync &> /dev/null; then
rsync -a --delete --exclude='.git' . /app/
else
rm -rf /app/dap-* /app/src /app/build.gradle /app/settings.gradle
cp -a . /app/
fi
- name: Deploy Task on Host
run: |
echo "Starting Standard CI/CD Deploy pipeline..."
# 1. gradle 공식 이미지로 바로 컴파일 빌드!
docker run --rm \
-v /home/ubuntu/app/ax_hub_mcp_tool:/app \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /home/ubuntu/.gradle:/home/gradle/.gradle \
-w /app \
gradle:8-jdk21 \
./gradlew bootJar -x test
# 1. runner에 설치된 java로 직접 빌드! (격리된 환경 문제 해결)
cd /app
chmod +x gradlew
./gradlew bootJar -x test
# 2. 빌드 결과 plain jar 아카이브 정리
find /app -name "*-plain.jar" -delete

View File

@@ -1,6 +1,6 @@
# DAP Backend
Spring Boot 기반 DAP 관리자 백엔드 API 서버 및 MCP(Model Context Protocol) Gateway / Tool 분산 서버 프로젝트입니다.
Spring Boot 기반 DAP 관리자 백엔드 API 서버 및 MCP(Model Context Protocol) Gateway / Tool 분산 서버 프로젝트 입니다.
---

View File

@@ -1,40 +0,0 @@
import re
# 1. Clean docker-compose.yml
with open('docker-compose.yml', 'r', encoding='utf-8') as f:
content = f.read()
# Remove tool-email and tool-payment blocks
content = re.sub(r'^\s*tool-(email|payment)-(blue|green):.*?^\s*- SPRING_PROFILES_ACTIVE=\$\{ACTIVE_PROFILE:-local\}\n\n?', '', content, flags=re.MULTILINE | re.DOTALL)
# Remove MCP fallback routes
content = re.sub(r'^\s*- MCP_GATEWAY_FALLBACK_ROUTES_(EMAIL|PAYMENT)=.*?\n', '', content, flags=re.MULTILINE)
with open('docker-compose.yml', 'w', encoding='utf-8') as f:
f.write(content)
# 2. Clean deploy.yml
with open('.gitea/workflows/deploy.yml', 'r', encoding='utf-8') as f:
lines = f.readlines()
new_lines = []
for line in lines:
if 'tool-email' in line or 'tool-payment' in line:
# For lines that have multiple tools (like docker compose stop), just remove the specific ones
if 'docker compose stop' in line:
line = re.sub(r' tool-(email|payment)-\$[A-Z_]+', '', line)
new_lines.append(line)
else:
new_lines.append(line)
with open('.gitea/workflows/deploy.yml', 'w', encoding='utf-8') as f:
f.writelines(new_lines)
# 3. Clean nginx/conf.d/default.conf
with open('nginx/conf.d/default.conf', 'r', encoding='utf-8') as f:
content = f.read()
content = re.sub(r'upstream tool-(email|payment) \{.*?\n\}\n\n?', '', content, flags=re.MULTILINE | re.DOTALL)
content = re.sub(r'server \{\n\s+listen 828[35];.*?\n\}\n\n?', '', content, flags=re.MULTILINE | re.DOTALL)
with open('nginx/conf.d/default.conf', 'w', encoding='utf-8') as f:
f.write(content)

View File

@@ -1,19 +1,8 @@
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY gradlew .
COPY gradle gradle
COPY build.gradle settings.gradle ./
COPY dap-tool-core dap-tool-core
COPY dap-gateway dap-gateway
RUN chmod +x gradlew
RUN ./gradlew clean :dap-gateway:build -x test
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN apk add --no-cache tzdata
ENV TZ=Asia/Seoul
COPY --from=builder /app/dap-gateway/build/libs/*-SNAPSHOT.jar app.jar
COPY dap-gateway/build/libs/*-SNAPSHOT.jar app.jar
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "app.jar"]

View File

@@ -173,6 +173,10 @@
.replace(/'/g, "'");
}
function removeMarkdownEmphasis(text) {
return text.replace(/\*\*/g, '');
}
async function sendMessage() {
const text = chatInput.value.trim();
if (!text || isLoading) return;
@@ -205,6 +209,7 @@
// 5. 스트림 읽기
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let responseText = '';
while (true) {
const { done, value } = await reader.read();
@@ -215,7 +220,8 @@
for (const line of lines) {
if (line.startsWith('data:')) {
const data = line.substring(5);
textContainer.innerHTML += escapeHtml(data);
responseText += data;
textContainer.textContent = removeMarkdownEmphasis(responseText);
scrollToBottom();
}
}

View File

@@ -5,10 +5,14 @@ import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import io.shinhanlife.dap.lib.annotation.McpParameter;
import io.shinhanlife.dap.lib.annotation.McpValidation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -31,17 +35,23 @@ public class JsonSchemaGenerator {
* Java DTO 클래스를 분석하여 MCP 규격의 완전한 JSON Schema를 생성합니다.
*/
public static Map<String, Object> generateSchema(Class<?> clazz) {
return generateSchema(clazz, new HashSet<>());
}
private static Map<String, Object> generateSchema(Class<?> clazz, Set<Class<?>> visiting) {
Map<String, Object> schema = new HashMap<>();
schema.put("type", "object");
if (!visiting.add(clazz)) {
return schema;
}
Map<String, Object> properties = new HashMap<>();
List<String> requiredList = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
Map<String, Object> fieldSchema = new HashMap<>();
Map<String, Object> fieldSchema = createFieldSchema(field, visiting);
// 1. 타입 매핑
fieldSchema.put("type", mapJavaTypeToJsonType(field.getType()));
// 2. 어노테이션 기반 설명 추출
McpParameter paramAnnotation = field.getAnnotation(McpParameter.class);
@@ -82,9 +92,47 @@ public class JsonSchemaGenerator {
schema.put("required", requiredList);
}
visiting.remove(clazz);
return schema;
}
private static Map<String, Object> createFieldSchema(Field field, Set<Class<?>> visiting) {
Class<?> fieldType = field.getType();
if (isSimpleType(fieldType)) {
return new HashMap<>(Map.of("type", mapJavaTypeToJsonType(fieldType)));
}
if (List.class.isAssignableFrom(fieldType)) {
Map<String, Object> fieldSchema = new HashMap<>();
fieldSchema.put("type", "array");
fieldSchema.put("items", generateItemsSchema(field, visiting));
return fieldSchema;
}
return generateSchema(fieldType, visiting);
}
private static Map<String, Object> generateItemsSchema(Field field, Set<Class<?>> visiting) {
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType parameterizedType) {
Type itemType = parameterizedType.getActualTypeArguments()[0];
if (itemType instanceof Class<?> itemClass) {
if (isSimpleType(itemClass)) {
return new HashMap<>(Map.of("type", mapJavaTypeToJsonType(itemClass)));
}
return generateSchema(itemClass, visiting);
}
}
return new HashMap<>(Map.of("type", "object"));
}
private static boolean isSimpleType(Class<?> clazz) {
return clazz == String.class
|| clazz == Integer.class || clazz == int.class
|| clazz == Long.class || clazz == long.class
|| clazz == Double.class || clazz == double.class
|| clazz == Float.class || clazz == float.class
|| clazz == Boolean.class || clazz == boolean.class;
}
private static String mapJavaTypeToJsonType(Class<?> clazz) {
if (clazz == String.class) return "string";
if (clazz == Integer.class || clazz == int.class) return "integer";

View File

@@ -190,11 +190,13 @@ public class BusinessToolController {
}
}
// 4. 메서드 실행
long startTime = System.currentTimeMillis();
Object methodResult = null;
methodResult = targetMethod.invoke(targetBean, invokeArgument);
if (targetMethod.getParameterCount() == 0) {
methodResult = targetMethod.invoke(targetBean);
} else {
methodResult = targetMethod.invoke(targetBean, invokeArgument);
}
long elapsed = System.currentTimeMillis() - startTime;

View File

@@ -1,12 +1,19 @@
package io.shinhanlife.dap.lib.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.ValidationMessage;
import io.shinhanlife.dap.lib.annotation.McpParameter;
import io.shinhanlife.dap.lib.annotation.McpValidation;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
/**
@@ -36,11 +43,55 @@ class JsonSchemaGeneratorTest {
assertEquals(List.of("APPROVE", "REJECT"), properties.get("approvalStatus").get("enum"));
}
@Test
void includesNestedDtoConstraintsInGeneratedSchema() {
Map<String, Object> schema = JsonSchemaGenerator.generateSchema(NestedRequest.class);
Map<String, Object> childSchema = property(schema, "child");
assertEquals("object", childSchema.get("type"));
assertTrue(required(childSchema).contains("businessDate"));
assertEquals("^\\d{8}$", property(childSchema, "businessDate").get("pattern"));
}
@Test
void includesNestedDtoSchemaForListItems() {
Map<String, Object> schema = JsonSchemaGenerator.generateSchema(ListRequest.class);
Map<String, Object> itemSchema = map(property(schema, "items").get("items"));
assertEquals("object", itemSchema.get("type"));
assertTrue(required(itemSchema).contains("businessDate"));
}
@Test
void validatorRejectsInvalidNestedValue() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)
.getSchema(objectMapper.writeValueAsString(JsonSchemaGenerator.generateSchema(NestedRequest.class)));
Set<ValidationMessage> errors = schema.validate(objectMapper.valueToTree(Map.of(
"child", Map.of("businessDate", "2026-07-28"))));
assertFalse(errors.isEmpty());
}
@SuppressWarnings("unchecked")
private Map<String, Map<String, Object>> properties(Map<String, Object> schema) {
return (Map<String, Map<String, Object>>) schema.get("properties");
}
private Map<String, Object> property(Map<String, Object> schema, String name) {
return properties(schema).get(name);
}
@SuppressWarnings("unchecked")
private Map<String, Object> map(Object value) {
return (Map<String, Object>) value;
}
@SuppressWarnings("unchecked")
private List<String> required(Map<String, Object> schema) {
return (List<String>) schema.get("required");
}
private static class ValidatedRequest {
@McpParameter(description = "recipient phone number", required = true)
@McpValidation(pattern = "^01[0-9]{8,9}$")
@@ -54,4 +105,17 @@ class JsonSchemaGeneratorTest {
@McpValidation(required = true, allowedValues = {"APPROVE", "REJECT"})
private String approvalStatus;
}
private static class NestedRequest {
private NestedChild child;
}
private static class ListRequest {
private List<NestedChild> items;
}
private static class NestedChild {
@McpValidation(required = true, pattern = "^\\d{8}$")
private String businessDate;
}
}

View File

@@ -1,19 +1,8 @@
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY gradlew .
COPY gradle gradle
COPY build.gradle settings.gradle ./
COPY dap-tool-core dap-tool-core
COPY dap-tool-oth dap-tool-oth
RUN chmod +x gradlew
RUN ./gradlew clean :dap-tool-oth:build -x test
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN apk add --no-cache tzdata
ENV TZ=Asia/Seoul
COPY --from=builder /app/dap-tool-oth/build/libs/*-SNAPSHOT.jar app.jar
COPY dap-tool-oth/build/libs/*-SNAPSHOT.jar app.jar
EXPOSE 8084
ENTRYPOINT ["java", "-jar", "app.jar"]

View File

@@ -0,0 +1,31 @@
package io.shinhanlife.dap.mcc.biz.cmm.converter;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse;
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.converter
* @className MetaCommonCodeConverter
* @description AX HUB 시스템 처리 클래스
* @author 09863409
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 09863409 최초생성
*
* </pre>
*/
@Mapper(componentModel = "spring")
public interface MetaCommonCodeConverter {
@Mapping(target = "csNo", source = "groupCode", defaultValue = "GRP_COMM_CD")
CLCNNB00001_I toLegacyRequest(MetaCommonCodeRequest req);
@Mapping(target = "codeList", ignore = true)
MetaCommonCodeResponse toResponse(CLCNNB00001_O mciRes);
}

View File

@@ -21,7 +21,7 @@ import lombok.Data;
@Data
public class BalanceRequest {
@McpParameter(description = "고객의 계좌번호 (- 제외)", required = true)
@McpParameter(description = "고객의 계좌번호 (- 제외) ", required = true)
@McpValidation(pattern = "\\S")
private String accountNumber;
}

View File

@@ -0,0 +1,32 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.shinhanlife.dap.lib.annotation.McpParameter;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.dto
* @className MetaCommonCodeRequest
* @description AX HUB 시스템 처리 클래스
* @author 09863409
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 09863409 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MetaCommonCodeRequest {
@McpParameter(description = "통합코드 그룹 ID (예: GRP_SYS_01, GRP_COMM_CD)", required = false)
private String groupCode;
@McpParameter(description = "코드명 검색 키워드 (예: 사용, 상태)", required = false)
private String codeName;
@McpParameter(description = "사용여부 (예: Y, N)", required = false)
private String useYn;
}

View File

@@ -0,0 +1,37 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.util.List;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.dto
* @className MetaCommonCodeResponse
* @description AX HUB 시스템 처리 클래스
* @author 09863409
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 09863409 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MetaCommonCodeResponse {
private List<MetaCommonCodeItem> codeList;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class MetaCommonCodeItem {
private String groupCode;
private String code;
private String codeName;
private String codeDesc;
private Integer sortSeq;
private String useYn;
}
}

View File

@@ -0,0 +1,37 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
import io.shinhanlife.dap.lib.annotation.McpFunction;
import io.shinhanlife.dap.lib.annotation.McpTool;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
* @className MetaCommonCodeUseCase
* @description AX HUB 시스템 처리 클래스
* @author 09863409
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 09863409 최초생성
*
* </pre>
*/
@McpTool(
routingType = "MCI",
categoryKey = "cmm"
)
public interface MetaCommonCodeUseCase {
@McpFunction(
displayName = "메타 통합코드 조회 툴",
name = "metaCommonCode",
description = "메타 통합코드 목록을 조회해줘",
prompt = "메타 통합코드 목록을 조회해줘",
mappingId = "CLCNNB00001",
register = false,
requiresApproval = false,
openWorldHint = true
)
Object execute(MetaCommonCodeRequest req);
}

View File

@@ -4,6 +4,8 @@ import io.shinhanlife.dap.lib.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.McpFunction;
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
import java.util.Map;
@McpTool(
routingType = "HTTP",
categoryKey = "cmm"
@@ -15,5 +17,5 @@ public interface TemplateUtilityUseCase {
description = "요청한 템플릿(엑셀, 워드 등) 파일(양식)을 다운로드 받을 수 있는 시스템 URL을 반환합니다. AI는 이 URL을 사용자에게 마크다운 링크 형태로 제공해야 합니다.",
prompt = "요청하신 템플릿 양식 파일 다운로드 URL은 다음과 같습니다. 클릭하여 다운로드하세요:"
)
java.util.Map<String, Object> getTemplateFileUrl(TemplateDownloadRequest req);
Map<String, Object> getTemplateFileUrl(TemplateDownloadRequest req);
}

View File

@@ -0,0 +1,89 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl;
import io.shinhanlife.dap.mcc.biz.cmm.converter.MetaCommonCodeConverter;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse.MetaCommonCodeItem;
import io.shinhanlife.dap.mcc.biz.cmm.usecase.MetaCommonCodeUseCase;
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient;
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl
* @className MetaCommonCodeUseCaseImpl
* @description AX HUB 시스템 처리 클래스
* @author 09863409
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 09863409 최초생성
*
* </pre>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MetaCommonCodeUseCaseImpl implements MetaCommonCodeUseCase {
private final MciCfpaClient mci;
private final MetaCommonCodeConverter converter;
@Override
public Object execute(MetaCommonCodeRequest req) {
log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaCommonCode", req);
try {
CLCNNB00001_I mciRequest = converter.toLegacyRequest(req);
Object mciResponse = mci.callCfpa0001(mciRequest);
log.info("[MCI Tool] CLCNNB00001 MCI call completed. Returning response status: {}",
mciResponse != null);
// 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 메타 통합코드 샘플 결과를 반환합니다.
MetaCommonCodeResponse res = new MetaCommonCodeResponse();
List<MetaCommonCodeItem> list = new ArrayList<>();
MetaCommonCodeItem item1 = new MetaCommonCodeItem();
item1.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD");
item1.setCode("CD001");
item1.setCodeName("진행중");
item1.setCodeDesc("SR 요청 처리 진행 중 상태");
item1.setSortSeq(1);
item1.setUseYn("Y");
list.add(item1);
MetaCommonCodeItem item2 = new MetaCommonCodeItem();
item2.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD");
item2.setCode("CD002");
item2.setCodeName("완료");
item2.setCodeDesc("SR 요청 처리 완료 상태");
item2.setSortSeq(2);
item2.setUseYn("Y");
list.add(item2);
MetaCommonCodeItem item3 = new MetaCommonCodeItem();
item3.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD");
item3.setCode("CD003");
item3.setCodeName("보류");
item3.setCodeDesc("SR 요청 처리 일시 보류 상태");
item3.setSortSeq(3);
item3.setUseYn("N");
list.add(item3);
res.setCodeList(list);
return res;
} catch (Exception e) {
log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e);
return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error");
}
}
}

View File

@@ -0,0 +1,26 @@
package io.shinhanlife.dap.mcc.biz.smp.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.shinhanlife.dap.lib.annotation.McpParameter;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.biz.smp.dto
* @className TeamMemberRequest
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class TeamMemberRequest {
@McpParameter(description = "조회할 팀 이름 (예: AX, MCP, TOOL, 전체 등)", required = false)
private String teamName;
}

View File

@@ -0,0 +1,24 @@
package io.shinhanlife.dap.mcc.biz.smp.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.biz.smp.dto
* @className TeamMemberResponse
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class TeamMemberResponse {
private String result;
}

View File

@@ -0,0 +1,23 @@
package io.shinhanlife.dap.mcc.biz.smp.usecase;
import io.shinhanlife.dap.lib.annotation.McpFunction;
import io.shinhanlife.dap.lib.annotation.McpTool;
import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberRequest;
@McpTool(
routingType = "DIRECT",
categoryKey = "smp"
)
public interface TeamMemberUseCase {
@McpFunction(
displayName = "신한라이프 MCP, TOOL 파트 구성원 조회",
name = "get_smp_members",
description = "신한라이프 MCP, TOOL 파트 구성원을 조회합니다.",
prompt = "신한라이프 MCP, TOOL 파트 구성원을 조회해 줘. (주의: 응답 시 ** 등 마크다운 기호를 절대 사용하지 말고 평문으로만 출력해 줘)",
mappingId = "DIRECT0001",
register = false,
requiresApproval = false,
openWorldHint = true
)
Object execute(TeamMemberRequest req);
}

View File

@@ -0,0 +1,54 @@
package io.shinhanlife.dap.mcc.biz.smp.usecase.impl;
import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberRequest;
import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberResponse;
import io.shinhanlife.dap.mcc.biz.smp.usecase.TeamMemberUseCase;
import io.shinhanlife.dap.mcc.usecase.AbstractMcpToolUseCase;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* @package io.shinhanlife.dap.mcc.biz.smp.usecase.impl
* @className TeamMemberUseCaseImpl
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class TeamMemberUseCaseImpl extends AbstractMcpToolUseCase implements TeamMemberUseCase {
@Override
public Object execute(TeamMemberRequest req) {
log.info("[A01] 신한라이프 MCP, TOOL 파트 구성원 조회 요청: {}", req);
String filter = req != null && req.getTeamName() != null ? req.getTeamName().toUpperCase() : "전체";
String resultString = "";
if (filter.contains("AX")) {
resultString += "ax 추진팀 박세진 프로\n";
} else if (filter.contains("MCP") && !filter.contains("TOOL")) {
resultString += "MCP 팀은 고석민 수석 , 장효원 책임\n";
} else if (filter.contains("TOOL") && !filter.contains("MCP")) {
resultString += "TOOL 팀은 김형식 수석 ,김영진 책임 , 김도겸 대리 , 이주희 선임 , 문주현 선임 , 이보람 대리 , 박수빈 대리\n";
} else {
resultString += "ax 추진팀 박세진 프로\n" +
"MCP & TOOL 팀 담당자는 윤희준 이사\n" +
"MCP 팀은 고석민 수석 , 장효원 책임\n" +
"TOOL 팀은 김형식 수석 ,김영진 책임 , 김도겸 대리 , 이주희 선임 , 문주현 선임 , 이보람 대리 , 박수빈 대리";
}
TeamMemberResponse res = new TeamMemberResponse();
res.setResult(resultString.trim());
return res;
}
}

View File

@@ -0,0 +1,33 @@
package io.shinhanlife.dap.mcc.biz.sol.converter;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_O;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* @package io.shinhanlife.dap.mcc.biz.sol.converter
* @className SolReqListConverter
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Mapper(componentModel = "spring")
public interface SolReqListConverter {
@Mapping(source = "status", target = "reqStatus", defaultValue = "진행중")
@Mapping(source = "period", target = "reqPeriod", defaultValue = "최근 3개월")
@Mapping(source = "target", target = "reqTarget", defaultValue = "나의 업무")
SOLG00000001_I toLegacyRequest(SolReqListRequest req);
SolReqListResponse toResponse(SOLG00000001_O mciRes);
}

View File

@@ -0,0 +1,32 @@
package io.shinhanlife.dap.mcc.biz.sol.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.shinhanlife.dap.lib.annotation.McpParameter;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.biz.sol.dto
* @className SolReqListRequest
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SolReqListRequest {
@McpParameter(description = "진행상태 (예: 진행중, 완료 등)", required = false)
private String status;
@McpParameter(description = "조회기간 (예: 1개월, 3개월 등)", required = false)
private String period;
@McpParameter(description = "조회대상 (예: 나의 업무, 전체 등)", required = false)
private String target;
}

View File

@@ -0,0 +1,37 @@
package io.shinhanlife.dap.mcc.biz.sol.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.util.List;
/**
* @package io.shinhanlife.dap.mcc.biz.sol.dto
* @className SolReqListResponse
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SolReqListResponse {
private List<SolReqListItem> reqList;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class SolReqListItem {
private String srId;
private String srName;
private String process;
private String devStage;
private String appName;
private String requester;
}
}

View File

@@ -0,0 +1,23 @@
package io.shinhanlife.dap.mcc.biz.sol.usecase;
import io.shinhanlife.dap.lib.annotation.McpFunction;
import io.shinhanlife.dap.lib.annotation.McpTool;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest;
@McpTool(
routingType = "MCI",
categoryKey = "sol"
)
public interface SolReqListUseCase {
@McpFunction(
displayName = "SolReqList 툴",
name = "solReqList",
description = "SOL 의뢰서 목록 조회해줘",
prompt = "SOL 의뢰서 목록 조회해줘",
mappingId = "SOLG00000001",
register = false,
requiresApproval = false,
openWorldHint = true
)
Object execute(SolReqListRequest req);
}

View File

@@ -0,0 +1,80 @@
package io.shinhanlife.dap.mcc.biz.sol.usecase.impl;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse;
import io.shinhanlife.dap.mcc.biz.sol.usecase.SolReqListUseCase;
import io.shinhanlife.glow.communication.dto.Transfer;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqListConverter;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_O;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse.SolReqListItem;
/**
* @package io.shinhanlife.dap.mcc.biz.sol.usecase.impl
* @className SolReqListUseCaseImpl
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SolReqListUseCaseImpl implements SolReqListUseCase {
private final MciNclgClient mci;
private final SolReqListConverter converter;
@Override
public Object execute(SolReqListRequest req) {
log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "solReqList", req);
try {
SOLG00000001_I mciRequest = converter.toLegacyRequest(req);
Transfer<SOLG00000001_O> mciResponse = mci.callTo(
"SOLG00000001", "SOLG00000001", mciRequest, SOLG00000001_O.class);
log.info("[MCI Tool] SOLG00000001 MCI call completed. Returning dummy response: {}",
mciResponse != null);
// 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 결과를 반환합니다.
SolReqListResponse res = new SolReqListResponse();
List<SolReqListItem> list = new ArrayList<>();
SolReqListItem item1 = new SolReqListItem();
item1.setSrId("SR-2026-001");
item1.setSrName("AX HUB 메인 화면 UI 개편");
item1.setProcess("진행중");
item1.setDevStage("개발(단위테스트)");
item1.setAppName("AX HUB");
item1.setRequester("윤희준");
list.add(item1);
SolReqListItem item2 = new SolReqListItem();
item2.setSrId("SR-2026-002");
item2.setSrName("툴 연동 모듈 추가 개발");
item2.setProcess("진행중");
item2.setDevStage("분석/설계");
item2.setAppName("MCP Gateway");
item2.setRequester("고석민");
list.add(item2);
res.setReqList(list);
return res;
} catch (Exception e) {
log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e);
return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error");
}
}
}

View File

@@ -0,0 +1,30 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;
import io.shinhanlife.glow.communication.dto.Transfer;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g
* @className MciNclgClient
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Component
@RequiredArgsConstructor
public class MciNclgClient {
private final AxhubMciComponent mci;
public <T> Transfer<T> callTo(String interfaceId, String dummy, Object mciReq, Class<T> resType) throws Exception {
return mci.callTo(interfaceId, dummy, mciReq, resType);
}
}

View File

@@ -0,0 +1,24 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io
* @className SOLG00000001_I
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Data
public class SOLG00000001_I {
private String reqStatus;
private String reqPeriod;
private String reqTarget;
}

View File

@@ -0,0 +1,34 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io;
import lombok.Data;
import java.util.List;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io
* @className SOLG00000001_O
* @description AX HUB 시스템 처리 클래스
* @author jade
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 jade 최초생성
*
* </pre>
*/
@Data
public class SOLG00000001_O {
private List<SOLG00000001_O_Item> reqList;
@Data
public static class SOLG00000001_O_Item {
private String srId;
private String srName;
private String process;
private String devStage;
private String appName;
private String requester;
}
}

View File

@@ -0,0 +1,39 @@
package io.shinhanlife.dap.mcc.biz.sol.usecase.impl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqListConverter;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_O;
import io.shinhanlife.glow.communication.dto.Transfer;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class SolReqListUseCaseImplTest {
@Test
void callsMciWithConvertedRequestAndReturnsDummyResponse() throws Exception {
MciNclgClient mci = Mockito.mock(MciNclgClient.class);
SolReqListConverter converter = Mockito.mock(SolReqListConverter.class);
SolReqListUseCaseImpl useCase = new SolReqListUseCaseImpl(mci, converter);
SolReqListRequest request = new SolReqListRequest();
SOLG00000001_I mciRequest = new SOLG00000001_I();
when(converter.toLegacyRequest(request)).thenReturn(mciRequest);
when(mci.callTo(eq("SOLG00000001"), eq("SOLG00000001"), eq(mciRequest), eq(SOLG00000001_O.class)))
.thenReturn(new Transfer<>());
SolReqListResponse response = (SolReqListResponse) useCase.execute(request);
verify(converter).toLegacyRequest(request);
verify(mci).callTo("SOLG00000001", "SOLG00000001", mciRequest, SOLG00000001_O.class);
assertThat(response.getReqList()).extracting(SolReqListResponse.SolReqListItem::getSrId)
.containsExactly("SR-2026-001", "SR-2026-002");
}
}

View File

@@ -1,19 +1,8 @@
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY gradlew .
COPY gradle gradle
COPY build.gradle settings.gradle ./
COPY dap-tool-core dap-tool-core
COPY dap-tool-sms dap-tool-sms
RUN chmod +x gradlew
RUN ./gradlew clean :dap-tool-sms:build -x test
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN apk add --no-cache tzdata
ENV TZ=Asia/Seoul
COPY --from=builder /app/dap-tool-sms/build/libs/*-SNAPSHOT.jar app.jar
COPY dap-tool-sms/build/libs/*-SNAPSHOT.jar app.jar
EXPOSE 8082
ENTRYPOINT ["java", "-jar", "app.jar"]

View File

@@ -1,34 +0,0 @@
$ErrorActionPreference = "Stop"
$extensions = @("*.java", "*.xml", "*.yml", "*.properties", "*.gradle", "*.md")
$baseDir = "C:\eGovFrameDev-4.3.1-64bit\workspace-egov\axhub-backend-main"
Write-Host "Replacing text (Req -> Request, Res -> Response)..."
Get-ChildItem -Path $baseDir -Include $extensions -Recurse -File | ForEach-Object {
if ($_.FullName -notmatch "\\build\\" -and $_.FullName -notmatch "\\\.git\\" -and $_.FullName -notmatch "\\\.gradle\\" -and $_.FullName -notmatch "\\bin\\") {
try {
$content = Get-Content $_.FullName -Raw -Encoding UTF8
$newContent = $content -creplace "Req\b", "Request"
$newContent = $newContent -creplace "Res\b", "Response"
if ($content -cne $newContent) {
Write-Host "Updated text in: $($_.FullName)"
$utf8NoBom = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllText($_.FullName, $newContent, $utf8NoBom)
}
} catch {
Write-Host "Skipped binary or locked file: $($_.FullName)"
}
}
}
Write-Host "Renaming DTO files..."
Get-ChildItem -Path $baseDir -Include *Req.java,*Res.java -Recurse -File | ForEach-Object {
if ($_.FullName -notmatch "\\build\\" -and $_.FullName -notmatch "\\\.git\\" -and $_.FullName -notmatch "\\\.gradle\\" -and $_.FullName -notmatch "\\bin\\") {
$newName = $_.Name -creplace "Req\.java$", "Request.java"
$newName = $newName -creplace "Res\.java$", "Response.java"
Write-Host "Renaming file: $($_.FullName) -> $newName"
Rename-Item -Path $_.FullName -NewName $newName
}
}
Write-Host "DTO Refactoring completed!"

View File

@@ -1,33 +0,0 @@
$ErrorActionPreference = "Stop"
$extensions = @("*.java", "*.xml", "*.yml", "*.properties", "*.gradle", "*.md", "Dockerfile*")
$baseDir = "C:\eGovFrameDev-4.3.1-64bit\workspace-egov\axhub-backend-main"
Write-Host "Replacing text..."
Get-ChildItem -Path $baseDir -Include $extensions -Recurse -File | ForEach-Object {
if ($_.FullName -notmatch "\\build\\" -and $_.FullName -notmatch "\\\.git\\" -and $_.FullName -notmatch "\\\.gradle\\" -and $_.FullName -notmatch "\\bin\\") {
try {
$content = Get-Content $_.FullName -Raw -Encoding UTF8
$newContent = $content -replace "dapms", "mcg"
$newContent = $newContent -replace "dapmt", "mcc"
if ($content -cne $newContent) {
Write-Host "Updated text in: $($_.FullName)"
$utf8NoBom = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::WriteAllText($_.FullName, $newContent, $utf8NoBom)
}
} catch {
Write-Host "Skipped binary or locked file: $($_.FullName)"
}
}
}
Write-Host "Renaming directories..."
Get-ChildItem -Path $baseDir -Recurse -Directory | Where-Object { $_.Name -eq "dapms" -or $_.Name -eq "dapmt" } | Sort-Object -Property @{Expression={$_.FullName.Length}; Descending=$true} | ForEach-Object {
if ($_.FullName -notmatch "\\build\\" -and $_.FullName -notmatch "\\\.git\\" -and $_.FullName -notmatch "\\\.gradle\\" -and $_.FullName -notmatch "\\bin\\") {
$newName = if ($_.Name -eq "dapms") { "mcg" } else { "mcc" }
Write-Host "Renaming dir: $($_.FullName) -> $newName"
Rename-Item -Path $_.FullName -NewName $newName
}
}
Write-Host "Refactoring completed!"