refactor: apply Option B, remove heartbeat and register tools once on startup
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 44s

This commit is contained in:
jade
2026-08-13 17:34:32 +09:00
parent 34e678ad63
commit 8f1c64ac58
177 changed files with 718 additions and 170 deletions

View File

@@ -0,0 +1,29 @@
package io.shinhanlife.dap.mcc.biz.cmm.converter;
import io.shinhanlife.dap.mcc.biz.cmm.dto.CcCstUnfcNotiCarhDto;
import io.shinhanlife.dap.mcc.biz.cmm.dto.CustomerGuidanceToolRequest;
import io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io.ONILD0320_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io.ONILD0320_O.CstUnfcNotiCarhInqrOutDto;
import org.mapstruct.*;
import java.util.List;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface CustomerGuidanceToolConverter {
/**
* 고객통합이력조회 전사 Request -> I/F
*/
@Mapping(target = "cstUnfcNotiCarhInqrInDto.inqrEndYmd", source = "inqrEndYmd")
@Mapping(target = "cstUnfcNotiCarhInqrInDto.inqrStrtYmd", source = "inqrStrYmd")
@Mapping(target = "cstUnfcNotiCarhInqrInDto.notiPmlMdCd", source = "notiPmlMdCd")
@Mapping(target = "cstUnfcNotiCarhInqrInDto.ntleCd", source = "ntleCd")
@Mapping(target = "cstUnfcNotiCarhInqrInDto.csNo", source = "csNo")
public abstract ONILD0320_I toONILD0320_I(CustomerGuidanceToolRequest req);
@Named("toCcCstUnfcNotiCarhDto")
public abstract CcCstUnfcNotiCarhDto toCcCstUnfcNotiCarhDto(CstUnfcNotiCarhInqrOutDto cstUnfcNotiCarhInqrOutDto);
@IterableMapping(qualifiedByName = "toCcCstUnfcNotiCarhDto")
public abstract List<CcCstUnfcNotiCarhDto> toCcCstUnfcNotiCarhDtoList(List<CstUnfcNotiCarhInqrOutDto> cstUnfcNotiCarhInqrOutDto);
}

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

@@ -0,0 +1,31 @@
package io.shinhanlife.dap.mcc.biz.cmm.converter;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse;
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 MetaTableConverter
* @description AX HUB 시스템 처리 클래스
* @author 09863409
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 09863409 최초생성
*
* </pre>
*/
@Mapper(componentModel = "spring")
public interface MetaTableConverter {
@Mapping(target = "csNo", source = "tableName", defaultValue = "TB_META_BAS")
CLCNNB00001_I toLegacyRequest(MetaTableRequest req);
@Mapping(target = "tableList", ignore = true)
MetaTableResponse toResponse(CLCNNB00001_O mciRes);
}

View File

@@ -0,0 +1,29 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AnalysisDataQueryRequest {
@Schema(description = "데이터 조회를 시작할 날짜", example = "2024-01-01")
private String startDate;
@Schema(description = "데이터 조회를 종료할 날짜", example = "2024-12-31")
private String endDate;
@Schema(description = "분석할 데이터 카테고리", example = "sales")
private String category;
@Schema(description = "지역 필터", example = "seoul")
private String region;
@Schema(description = "조회할 결과 수 제한", example = "100")
private Integer limit;
@Schema(description = "조회할 결과 시작 위치", example = "0")
private Integer offset;
}

View File

@@ -0,0 +1,97 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
@Data
public class CcCstUnfcNotiCarhDto {
@Schema(description = "고객번호", example = "CUST123")
private String csNo;
@Schema(description = "안내일시", example = "20260812093000")
private String notiDt;
@Schema(description = "발송일련번호", example = "1")
private int pmlSriaNo;
@Schema(description = "안내신청상세일시", example = "20260812092500")
private String notiPetDtptDt;
@Schema(description = "안내장코드", example = "NT001")
private String ntleCd;
@Schema(description = "안내장명", example = "카드대금 결제일 안내")
private String ntleNm;
@Schema(description = "안내발송일련번호", example = "10")
private int notiPmlSriaNo;
@Schema(description = "안내발송방법코드", example = "SMS")
private String notiPmlMdCd;
@Schema(description = "안내수신정보암호화내용", example = "AbC123EncRypTed==")
private String notiRcvInfoEncrCt;
@Schema(description = "메시지ID", example = "MSG20260812000001")
private String msgId;
@Schema(description = "안내내용", example = "8월 카드대금 결제일은 8월 25일입니다.")
private String notiCt;
@Schema(description = "통합안내발송결과코드", example = "00")
private String unfcNotiPmlRsltCd;
@Schema(description = "통합안내발송결과상세코드", example = "0001")
private String unfcNotiPmlRsltDtptCd;
@Schema(description = "발송시스템코드", example = "SYS01")
private String pmlSystCd;
@Schema(description = "발송조직번호", example = "1001")
private String pmlOgnzNo;
@Schema(description = "발송조직명", example = "고객지원팀")
private String pmlOgnzNm;
@Schema(description = "발신기관조직명", example = "카드사업본부")
private String msdilttOgnzNm;
@Schema(description = "조회구분명", example = "전체")
private String inqrDvsnNm;
@Schema(description = "등록일시", example = "20260812090000")
private String rgiDt;
@Schema(description = "등록자명", example = "홍길동")
private String rgstNm;
@Schema(description = "등록자조직명", example = "고객지원팀")
private String rgstOgnzNm;
@Schema(description = "최종변경자명", example = "김철수")
private String lstPrwpNm;
@Schema(description = "최종변경일시", example = "20260812101500")
private String lstChgDt;
@Schema(description = "최종변경자조직명", example = "운영지원팀")
private String lstPrwpOgnzNm;
@Schema(description = "원천회사구분코드", example = "01")
private String oriCpnyDvsnCd;
@Schema(description = "고객한글명", example = "이보람")
private String cstHanNm;
@Schema(description = "시스템등록인사번호", example = "20260001")
private String systRgiPrafNo;
@Schema(description = "시스템변경인사번호", example = "20260002")
private String systChgPrafNo;
}

View File

@@ -0,0 +1,27 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
@Data
@Getter
@Builder
public class CustomerGuidanceToolRequest {
@Schema(description = "고객번호", example = "CUST123")
private String csNo;
@Schema(description = "안내장코드", example = "ab12321")
private String ntleCd;
@Schema(description = "안내발송방법코드", example = "aa")
private String notiPmlMdCd;
@Schema(description = "조회시작일자", example = "20260101")
private String inqrStrYmd;
@Schema(description = "조회종료일자", example = "20260101")
private String inqrEndYmd;
}

View File

@@ -0,0 +1,21 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.List;
@Getter
@Builder
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CustomerGuidanceToolResponse {
private String resultCode;
private String resultMessage;
private List<CcCstUnfcNotiCarhDto> ccCstUnfcNotiCarhDtoList;
}

View File

@@ -0,0 +1,32 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.shinhanlife.glow.GlowMciFieldInfo;
import lombok.Data;
import java.util.List;
/**
* @package io.shinhanlife.dap.mcc.dto
* @className MciSampleStringResponse
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MciSampleStringResponse {
@GlowMciFieldInfo(order = 1, length = 10, description = "이름")
private String name;
@GlowMciFieldInfo(order = 2, length = 3, description = "나이")
private int age;
@GlowMciFieldInfo(order = 3, length = 8, description = "가입일자(YYYYMMDD)")
private String joinDate;
@GlowMciFieldInfo(order = 4, length = 2, description = "상태코드")
private String statusCode;
@GlowMciFieldInfo(order = 5, length = 30, description = "타겟 리스트", target = MciSampleTargetDto.class)
private List<MciSampleTargetDto> targetList;
}

View File

@@ -0,0 +1,20 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import io.shinhanlife.glow.GlowMciFieldInfo;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.dto
* @className MciSampleTargetDto
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
*/
@Data
public class MciSampleTargetDto {
@GlowMciFieldInfo(order = 1, length = 5, description = "항목 코드")
private String itemCode;
@GlowMciFieldInfo(order = 2, length = 5, description = "항목 값")
private String itemValue;
}

View File

@@ -0,0 +1,39 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springaicommunity.mcp.annotation.McpToolParam;
import com.fasterxml.jackson.annotation.JsonInclude;
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 {
@McpToolParam(description = "통합코드 그룹 ID (예: GRP_SYS_01, GRP_COMM_CD)", required = false)
@Schema(example = "GRP_001")
private String groupCode;
@McpToolParam(description = "코드명 검색 키워드 (예: 사용, 상태)", required = false)
private String codeName;
@McpToolParam(description = "사용여부 (예: Y, N)", required = false)
@Schema(example = "Y")
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,40 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springaicommunity.mcp.annotation.McpToolParam;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.dto
* @className MetaTableRequest
* @description AX HUB 시스템 처리 클래스
* @author 09863409
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 09863409 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MetaTableRequest {
@McpToolParam(description = "테이블 물리명 키워드 (예: TB_CUST_BAS, TB_CONT)", required = false)
@Schema(example = "TB_USER")
private String tableName;
@McpToolParam(description = "테이블 논리명(한글) 키워드 (예: 고객기본, 계약)", required = false)
@Schema(example = "고객기본")
private String tableLogicalName;
@McpToolParam(description = "스키마/소유자명 (예: DAPADM, SHLOWN)", required = false)
@Schema(example = "DAPADM")
private String owner;
}

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 MetaTableResponse
* @description AX HUB 시스템 처리 클래스
* @author 09863409
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 09863409 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MetaTableResponse {
private List<MetaTableItem> tableList;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class MetaTableItem {
private String owner;
private String tableName;
private String tableLogicalName;
private String tableDesc;
private Integer columnCount;
private Long rowCount;
}
}

View File

@@ -0,0 +1,27 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.dto
* @className SampleStringRequest
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SampleStringRequest {
@Schema(example = "test query")
private String query;
}

View File

@@ -0,0 +1,38 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.shinhanlife.glow.communication.annotation.GlowTrgmField;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.dto
* @className SampleStringResponse
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SampleStringResponse {
@GlowTrgmField(order = 1, length = 10, description = "이름")
private String name;
@GlowTrgmField(order = 2, length = 3, description = "나이")
private int age;
@GlowTrgmField(order = 3, length = 8, description = "가입일자(YYYYMMDD)")
private String joinDate;
@GlowTrgmField(order = 4, length = 2, description = "상태코드")
private String statusCode;
@GlowTrgmField(order = 5, length = 10, description = "타겟")
private String target;
}

View File

@@ -0,0 +1,38 @@
package io.shinhanlife.dap.mcc.biz.cmm.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @package io.shinhanlife.dap.mcc.dto
* @className TemplateDownloadRequest
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Getter
@Builder
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class TemplateDownloadRequest {
/**
* 다운로드할 템플릿의 종류 ID (예: CUSTOMER_EXCEL, PRODUCT_PDF 등)
*/
@Schema(example = "TPL_001")
private String templateId;
}

View File

@@ -0,0 +1,30 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
import io.shinhanlife.glow.BaseResponse;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.mcc.biz.cmm.dto.CustomerGuidanceToolRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.CustomerGuidanceToolResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
* @className CustomerGuidanceToolUseCase
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author Boram
* @create 2026.08.12
* <pre>
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* 2026.08.12 Boram 理쒖큹?앹꽦
*
* </pre>
*/
public interface CustomerGuidanceToolUseCase {
@McpTool(name = "cmm_customer_tool", title = "고객 통합 안내이력 조회", description = "고객의 통합 안내 이력을 조회하는 도구입니다. 고객 ID와 조회 기간을 입력하면 해당 기간 동안의 안내 이력을 반환합니다.")
@GrowToolHint(categoryKey = "cmm", mappingId = "ONILD0320")
CustomerGuidanceToolResponse searchCustomerGuidance(CustomerGuidanceToolRequest req) throws Exception;
}

View File

@@ -0,0 +1,26 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
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>
*/
public interface MetaCommonCodeUseCase {
@McpTool(name = "cmm_comcode_lookup", title = "메타 공통코드 조회 툴", description = "메타 통합코드 목록을 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true))
@GrowToolHint(requiresApproval = false, categoryKey = "cmm", mappingId = "CLCNNB00001")
Object searchCommonCode(MetaCommonCodeRequest req);
}

View File

@@ -0,0 +1,26 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase
* @className MetaTableUseCase
* @description AX HUB 시스템 처리 클래스
* @author 09863409
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 09863409 최초생성
*
* </pre>
*/
public interface MetaTableUseCase {
@McpTool(name = "cmm_meta_table", title = "메타 테이블 조회 툴", description = "메타 테이블 정보 목록을 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true))
@GrowToolHint(requiresApproval = false, categoryKey = "cmm", mappingId = "CLCNNB00001")
Object searchMetaTable(MetaTableRequest req);
}

View File

@@ -0,0 +1,14 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.mcc.biz.cmm.dto.*;
import java.util.Map;
public interface TemplateUtilityUseCase {
@McpTool(name = "cmm_template_url", title = "템플릿 유틸리티 툴", description = "요청한 템플릿(엑셀, 워드 등) 파일(양식)을 다운로드 받을 수 있는 시스템 URL을 반환합니다. AI는 이 URL을 사용자에게 마크다운 링크 형태로 제공해야 합니다.")
@GrowToolHint(categoryKey = "cmm")
Map<String, Object> getTemplateFileUrl(TemplateDownloadRequest req);
}

View File

@@ -0,0 +1,58 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl;
import io.shinhanlife.dap.mcc.biz.cmm.dto.CcCstUnfcNotiCarhDto;
import io.shinhanlife.dap.mcc.biz.cmm.dto.CustomerGuidanceToolRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.CustomerGuidanceToolResponse;
import io.shinhanlife.dap.mcc.biz.cmm.usecase.CustomerGuidanceToolUseCase;
import io.shinhanlife.glow.BaseResponse;
import io.shinhanlife.glow.ResponseUtil;
import io.shinhanlife.glow.communication.dto.Transfer;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import io.shinhanlife.dap.mcc.biz.cmm.converter.CustomerGuidanceToolConverter;
import io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io.ONILD0320_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io.ONILD0320_O;
import io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.MciNildClient;
import java.util.List;
import java.util.Map;
/**
* @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl
* @className CustomerGuidanceToolUseCaseImpl
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author Boram
* @create 2026.08.12
* <pre>
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* 2026.08.12 Boram 理쒖큹?앹꽦
*
* </pre>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class CustomerGuidanceToolUseCaseImpl implements CustomerGuidanceToolUseCase {
private final MciNildClient mci;
private final CustomerGuidanceToolConverter converter;
@Override
public CustomerGuidanceToolResponse searchCustomerGuidance(CustomerGuidanceToolRequest req) throws Exception {
ONILD0320_I onild0320_i = converter.toONILD0320_I(req);
ONILD0320_O onild0320_o = mci.callOnild0320(onild0320_i);
List<CcCstUnfcNotiCarhDto> ccCstUnfcNotiCarhDtoList = converter.toCcCstUnfcNotiCarhDtoList(onild0320_o.getCstUnfcNotiCarhInqrOutDto());
return CustomerGuidanceToolResponse.builder()
.ccCstUnfcNotiCarhDtoList(ccCstUnfcNotiCarhDtoList)
.resultCode("SUCCESS")
.resultMessage("")
.build();
}
}

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 searchCommonCode(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,89 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl;
import io.shinhanlife.dap.mcc.biz.cmm.converter.MetaTableConverter;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse;
import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse.MetaTableItem;
import io.shinhanlife.dap.mcc.biz.cmm.usecase.MetaTableUseCase;
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 MetaTableUseCaseImpl
* @description AX HUB 시스템 처리 클래스
* @author 09863409
* @create 2026.07.29
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.29 09863409 최초생성
*
* </pre>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MetaTableUseCaseImpl implements MetaTableUseCase {
private final MciCfpaClient mci;
private final MetaTableConverter converter;
@Override
public Object searchMetaTable(MetaTableRequest req) {
log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaTable", 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 통신을 바이패스하고 하드코딩된 메타 테이블 샘플 결과를 반환합니다.
MetaTableResponse res = new MetaTableResponse();
List<MetaTableItem> list = new ArrayList<>();
MetaTableItem item1 = new MetaTableItem();
item1.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM");
item1.setTableName(req.getTableName() != null ? req.getTableName() : "TB_CUST_BAS");
item1.setTableLogicalName("고객기본정보");
item1.setTableDesc("고객 기본 프로필 및 인적사항 관리 테이블");
item1.setColumnCount(35);
item1.setRowCount(1250000L);
list.add(item1);
MetaTableItem item2 = new MetaTableItem();
item2.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM");
item2.setTableName("TB_CONT_MCD");
item2.setTableLogicalName("계약주계약정보");
item2.setTableDesc("보험 계약 주계약 상세 원장 테이블");
item2.setColumnCount(58);
item2.setRowCount(3400000L);
list.add(item2);
MetaTableItem item3 = new MetaTableItem();
item3.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM");
item3.setTableName("TB_CLAIM_DTL");
item3.setTableLogicalName("청구접수상세");
item3.setTableDesc("보험금 청구 접수 건별 내역 테이블");
item3.setColumnCount(42);
item3.setRowCount(890000L);
list.add(item3);
res.setTableList(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,57 @@
package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl;
import io.shinhanlife.dap.mcc.biz.cmm.usecase.TemplateUtilityUseCase;
import io.shinhanlife.dap.mcc.biz.cmm.dto.TemplateDownloadRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
/**
* @package io.shinhanlife.dap.mcc.service
* @className TemplateUtilityService
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
public class TemplateUtilityUseCaseImpl implements TemplateUtilityUseCase {
@Override
public Map<String, Object> getTemplateFileUrl(TemplateDownloadRequest data) {
try {
String templateId = (data != null && data.getTemplateId() != null) ? data.getTemplateId().toLowerCase() : "default";
log.info("MCP 툴 호출됨: get_template_file_url, 요청 템플릿 ID: {}", templateId);
String fileName = "sample_" + templateId + ".xlsx";
String downloadUrl = "https://axhub-file-server.shinhanlife.io/downloads/" + fileName;
Map<String, Object> result = new HashMap<>();
result.put("status", "success");
Map<String, Object> contract = new HashMap<>();
contract.put("fileName", fileName);
contract.put("downloadUrl", downloadUrl);
contract.put("message", "다운로드 링크가 성공적으로 생성되었습니다. AI는 이 링크를 마크다운 형식으로 사용자에게 전달해야 합니다.");
contract.put("status", "success");
result.put("contracts", Collections.singletonList(contract));
return result;
} catch (Exception e) {
log.error("getTemplateFileUrl 내부 예외 발생", e);
throw new RuntimeException("템플릿 URL 생성 실패", e);
}
}
}

View File

@@ -0,0 +1,17 @@
package io.shinhanlife.dap.mcc.biz.ins.converter;
import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorRequest;
import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorResponse;
import io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io.InsuranceClaimProcessorHttpRequest;
import io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io.InsuranceClaimProcessorHttpResponse;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface InsuranceClaimProcessorConverter {
// Field names differ? Add mappings like this before the method.
// @Mapping(source = "sourceField", target = "targetField")
InsuranceClaimProcessorHttpRequest toHttpRequest(InsuranceClaimProcessorRequest request);
InsuranceClaimProcessorResponse toResponse(InsuranceClaimProcessorHttpResponse httpResponse);
}

View File

@@ -0,0 +1,19 @@
package io.shinhanlife.dap.mcc.biz.ins.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class InsuranceClaimProcessorRequest {
@Schema(description = "보험 청구 번호", example = "CLM20230001", requiredMode = Schema.RequiredMode.REQUIRED)
private String claimNumber;
@Schema(description = "청구 금액", example = "1500000.00", requiredMode = Schema.RequiredMode.REQUIRED)
private Double claimAmount;
@Schema(description = "청구 일자 (YYYY-MM-DD)", example = "2023-09-15", requiredMode = Schema.RequiredMode.REQUIRED)
private String claimDate;
}

View File

@@ -0,0 +1,16 @@
package io.shinhanlife.dap.mcc.biz.ins.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class InsuranceClaimProcessorResponse {
private String resultCode;
private String resultMessage;
@Schema(description = "청구 처리 고유 식별자", example = "CLM20230001", requiredMode = Schema.RequiredMode.REQUIRED)
private String claimId;
}

View File

@@ -0,0 +1,27 @@
package io.shinhanlife.dap.mcc.biz.ins.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorRequest;
import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorResponse;
/**
* @package io.shinhanlife.dap.mcc.biz.ins.usecase
* @className InsuranceClaimProcessorUseCase
* @description AX HUB 시스템 처리 클래스
* @author Admin
* @create 2026.08.11
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.08.11 Admin 최초생성
*
* </pre>
*/
public interface InsuranceClaimProcessorUseCase {
@McpTool(name = "ins_insurance_processor", title = "보험금 청구", description = "보험금 청구 요청을 처리하고 결과를 반환하는 LLM 도구 가이드")
@GrowToolHint(categoryKey = "ins", mappingId = "CLAIM0000001")
InsuranceClaimProcessorResponse processInsuranceClaim(InsuranceClaimProcessorRequest req);
}

View File

@@ -0,0 +1,30 @@
package io.shinhanlife.dap.mcc.biz.ins.usecase.impl;
import io.shinhanlife.dap.mcc.biz.ins.converter.InsuranceClaimProcessorConverter;
import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorRequest;
import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorResponse;
import io.shinhanlife.dap.mcc.infra.itrf.http.insurance.InsuranceClient;
import io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io.InsuranceClaimProcessorHttpRequest;
import io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io.InsuranceClaimProcessorHttpResponse;
import io.shinhanlife.dap.mcc.biz.ins.usecase.InsuranceClaimProcessorUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class InsuranceClaimProcessorUseCaseImpl implements InsuranceClaimProcessorUseCase {
private final InsuranceClaimProcessorConverter converter;
private final InsuranceClient insuranceClient;
@Override
public InsuranceClaimProcessorResponse processInsuranceClaim(InsuranceClaimProcessorRequest req) {
InsuranceClaimProcessorHttpRequest httpRequest = converter.toHttpRequest(req);
InsuranceClaimProcessorHttpResponse httpResponse = insuranceClient.call(httpRequest, InsuranceClaimProcessorHttpResponse.class);
InsuranceClaimProcessorResponse response = converter.toResponse(httpResponse);
response.setResultCode("SUCCESS");
response.setResultMessage("HTTP API call completed.");
return response;
}
}

View File

@@ -0,0 +1,29 @@
package io.shinhanlife.dap.mcc.biz.oth.converter;
import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request;
import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I;
import org.mapstruct.Mapper;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.converter
* @className Onnba3011Converter
* @description Converts ONNBA tool input into the CLCNNB00001 MCI payload
* @author 0986406
* @create 2026.07.27
* <pre>
* ---------- revision history ----------
* date author description
* ---------- --------- ---------------------------
* 2026.07.27 0986406 initial creation
* </pre>
*/
@Mapper(componentModel = "spring")
public interface Onnba3011Converter {
CLCNNB00001_I toMciRequest(Onnba3011Request source);
CLCNNB00001_I.UnfcPrbuIrcoAdduDto toUnfcPrbuIrcoAddu(
Onnba3011Request.UnfcPrbuIrcoAdduDto source);
CLCNNB00001_I.SucoIspaBasDto toSucoIspaBas(Onnba3011Request.SucoIspaBasDto source);
}

View File

@@ -0,0 +1,119 @@
package io.shinhanlife.dap.mcc.biz.oth.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.dto
* @className Onnba3011ReqDto
* @description 보종By가입설계한도계산조회 MCI 요청 전문 (ONNBA3011_I)
* @author 0986406
* @create 2026.09.01
*/
@Data
public class Onnba3011Request {
@JsonPropertyDescription("통합기계약보험 (유형: gs, 길이: 72)")
@JsonProperty("unfcPrbuIrcoAddu")
private UnfcPrbuIrcoAdduDto unfcPrbuIrcoAddu;
@JsonPropertyDescription("처리구분코드 (길이: 1)")
@JsonProperty("dalScCd")
private String dalScCd;
@JsonPropertyDescription("고객청약관계 (길이: 2)")
@JsonProperty("cstSucoRltyCd")
private String cstSucoRltyCd;
@JsonPropertyDescription("고객번호 (길이: 12)")
@JsonProperty("csNo")
private String csNo;
@JsonPropertyDescription("주민등록번호 (길이: 50)")
@JsonProperty("rdreNo")
private String rdreNo;
@JsonPropertyDescription("통합급부계산 (길이: 1)")
@JsonProperty("unfcPvsCalReqYn")
private String unfcPvsCalReqYn;
@JsonPropertyDescription("한국신용정보 (길이: 1)")
@JsonProperty("kcisPymmTnnrRequest")
private String kcisPymmTnnrRequest;
@JsonPropertyDescription("한도초과여부 (길이: 1)")
@JsonProperty("lmovYn")
private String lmovYn;
@JsonPropertyDescription("일반경유승인 (길이: 1)")
@JsonProperty("genPsthApvTrgtYn")
private String genPsthApvTrgtYn;
@JsonPropertyDescription("보험사한도초과 (길이: 1)")
@JsonProperty("ircoLmovEcpbTrgtYn")
private String ircoLmovEcpbTrgtYn;
@JsonPropertyDescription("진단계산여부 (길이: 1)")
@JsonProperty("digCalYn")
private String digCalYn;
@JsonPropertyDescription("기계약포함진단 (길이: 1)")
@JsonProperty("prbuIciDigCalYn")
private String prbuIciDigCalYn;
@JsonPropertyDescription("청약심사기본Dto (유형: gs, 길이: 2532)")
@JsonProperty("sucoIspaBasDto")
private SucoIspaBasDto sucoIspaBasDto;
// ----- Nested DTO Classes -----
@Data
public static class UnfcPrbuIrcoAdduDto {
// 실제 필요한 하위 필드들 추가 (사진 생략부분)
}
@Data
public static class SucoIspaBasDto {
@JsonPropertyDescription("계약처리유형 (길이: 2)")
@JsonProperty("ccnDalTypCd")
private String ccnDalTypCd;
@JsonPropertyDescription("신계약입력경로 (길이: 2)")
@JsonProperty("nwcnptCursCd")
private String nwcnptCursCd;
@JsonPropertyDescription("개인단체계약 (길이: 2)")
@JsonProperty("induAsctScCd")
private String induAsctScCd;
@JsonPropertyDescription("모집조직번호 (길이: 7)")
@JsonProperty("cepeOgnzNo")
private String cepeOgnzNo;
@JsonPropertyDescription("모집자사번번호 (길이: 8)")
@JsonProperty("cepePrafNo")
private String cepePrafNo;
@JsonPropertyDescription("수금조직번호 (길이: 7)")
@JsonProperty("clmoOgnzNo")
private String clmoOgnzNo;
@JsonPropertyDescription("수금자사번번호 (길이: 8)")
@JsonProperty("clmoPrafNo")
private String clmoPrafNo;
@JsonPropertyDescription("청약일자 (길이: 20)")
@JsonProperty("sucoYmd")
private String sucoYmd;
@JsonPropertyDescription("발행일자 (길이: 20)")
@JsonProperty("ispDt")
private String ispDt;
@JsonPropertyDescription("계약일자 (길이: 20)")
@JsonProperty("contYmd")
private String contYmd;
}
}

View File

@@ -0,0 +1,11 @@
package io.shinhanlife.dap.mcc.biz.oth.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.mcc.biz.oth.dto.*;
public interface Onnba3011UseCase {
@McpTool(name = "oth_onnba3011_call", description = "Onnba3011 호출 툴")
@GrowToolHint(categoryKey = "oth")
Object callOnnba3011(Onnba3011Request req);
}

View File

@@ -0,0 +1,58 @@
package io.shinhanlife.dap.mcc.biz.oth.usecase.impl;
import io.shinhanlife.dap.mcc.biz.oth.converter.Onnba3011Converter;
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 io.shinhanlife.dap.mcc.biz.oth.usecase.Onnba3011UseCase;
import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @package io.shinhanlife.dap.mcc.service
* @className OnnbaMciToolService
* @description 보종By가입설계한도계산조회 MCI 연동 툴
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class Onnba3011UseCaseImpl implements Onnba3011UseCase {
private static final String INTERFACE_CODE_3011 = "CLCNNB00001";
// 공통 MCI Client 주입
private final MciCfpaClient mciCfpaClient;
private final Onnba3011Converter onnba3011Converter;
/**
* AI Agent가 호출하게 될 메서드입니다.
*/
@Override
public Object callOnnba3011(Onnba3011Request req) {
log.info("[MCI Tool] 보종By가입설계한도계산조회 요청 수신.");
try {
// MciCfpaClient를 통한 호출
CLCNNB00001_I mciRequest = onnba3011Converter.toMciRequest(req);
Object response = mciCfpaClient.callCfpa0001(mciRequest);
log.info("[MCI Tool] Glow 기반 MCI 연동 성공.");
// 결과 반환
return response != null ? response : "{\"status\":\"SUCCESS\", \"message\":\"GlowMciComponent 통신 완료\"}";
} catch (Exception e) {
log.error("[MCI Tool] MCI 연동 중 오류 발생: {}", e.getMessage(), e);
return "{\"status\":\"ERROR\", \"message\":\"MCI 통신 실패: " + e.getMessage() + "\"}";
}
}
}

View File

@@ -0,0 +1,19 @@
package io.shinhanlife.dap.mcc.biz.smp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springaicommunity.mcp.annotation.McpToolParam;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DailyQuoteRequest {
@McpToolParam(description = "카테고리 (예: 속담 등)", required = false)
@Schema(example = "속담")
private String category;
}

View File

@@ -0,0 +1,3 @@
package io.shinhanlife.dap.mcc.biz.smp.dto;
public record DailyQuoteResponse(String quote, String author) {}

View File

@@ -0,0 +1,18 @@
package io.shinhanlife.dap.mcc.biz.smp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springaicommunity.mcp.annotation.McpToolParam;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ExchangeRateRequest {
@McpToolParam(description = "환율 코드 (예: USD 등)", required = false)
@Schema(example = "USD")
private String currencyCode;
}

View File

@@ -0,0 +1,3 @@
package io.shinhanlife.dap.mcc.biz.smp.dto;
public record ExchangeRateResponse(String baseCurrency, String targetCurrency, double rate) {}

View File

@@ -0,0 +1,32 @@
package io.shinhanlife.dap.mcc.biz.smp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springaicommunity.mcp.annotation.McpToolParam;
import com.fasterxml.jackson.annotation.JsonInclude;
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 {
@McpToolParam(description = "조회할 팀 이름 (예: AX, MCP, TOOL, 전체 등)", required = false)
@Schema(example = "TOOL")
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,27 @@
package io.shinhanlife.dap.mcc.biz.smp.dto;
import org.springaicommunity.mcp.annotation.McpToolParam;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
/**
* @package io.shinhanlife.dap.mcc.dto
* @className WeatherRequest
* @description 기상 조회 요청 클래스
* @author 0986406
* @create 2026.07.14
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.14 0986406 최초생성
*
* </pre>
*/
public record WeatherRequest(
@McpToolParam(description = "도시를 입력하세여(예: 서울)", required = true)
String city
) {
}

View File

@@ -0,0 +1,24 @@
package io.shinhanlife.dap.mcc.biz.smp.dto;
/**
* @package io.shinhanlife.dap.mcc.dto
* @className WeatherResponse
* @description 기상 조회 응답 클래스
* @author 0986406
* @create 2026.07.14
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.14 0986406 최초생성
*
* </pre>
*/
public record WeatherResponse(
String city,
double temperature,
double windSpeed,
String reportTime,
String summary
) {
}

View File

@@ -0,0 +1,13 @@
package io.shinhanlife.dap.mcc.biz.smp.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteRequest;
import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteResponse;
public interface DailyQuoteToolUseCase {
@McpTool(name = "smp_quote_daily", title = "오늘의 명언 툴", description = "무작위로 영감을 주는 명언을 하나 가져옵니다.")
@GrowToolHint(categoryKey = "smp", mappingId = "QUOTE_001")
DailyQuoteResponse getDailyQuote(DailyQuoteRequest req);
}

View File

@@ -0,0 +1,15 @@
package io.shinhanlife.dap.mcc.biz.smp.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateRequest;
import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateResponse;
import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateRequest;
import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateResponse;
public interface ExchangeRateToolUseCase {
@McpTool(name = "smp_exchange_inquiry", title = "실시간 환율 조회 툴", description = "원하는 통화의 실시간 환율을 조회합니다. (예: USD, EUR, JPY)")
@GrowToolHint(categoryKey = "smp", mappingId = "EXCHANGE_001")
ExchangeRateResponse getExchangeRate(ExchangeRateRequest req);
}

View File

@@ -0,0 +1,12 @@
package io.shinhanlife.dap.mcc.biz.smp.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberRequest;
public interface TeamMemberUseCase {
@McpTool(name = "smp_team_list", title = "신한라이프 MCP, TOOL 파트 구성원 조회", description = "신한라이프 MCP, TOOL 파트 구성원을 조회합니다.", annotations = @McpTool.McpAnnotations(openWorldHint = true))
@GrowToolHint(requiresApproval = false, categoryKey = "smp", mappingId = "DIRECT0001")
Object getTeamMember(TeamMemberRequest req);
}

View File

@@ -0,0 +1,12 @@
package io.shinhanlife.dap.mcc.biz.smp.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.mcc.biz.smp.dto.*;
public interface WeatherToolUseCase {
@McpTool(name = "smp_weather_inquiry", title = "날씨 조회 툴", description = "특정 도시의 현재 날씨, 온도, 풍속 정보를 조회합니다.")
@GrowToolHint(categoryKey = "smp", mappingId = "WEATHER_001")
WeatherResponse getWeather(WeatherRequest req);
}

View File

@@ -0,0 +1,45 @@
package io.shinhanlife.dap.mcc.biz.smp.usecase.impl;
import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteRequest;
import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteResponse;
import io.shinhanlife.dap.mcc.biz.smp.usecase.DailyQuoteToolUseCase;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Random;
/**
* @package io.shinhanlife.dap.mcc.service
* @className DailyQuoteToolService
* @description 랜덤 명언 제공 툴
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Slf4j
@Service
public class DailyQuoteToolUseCaseImpl implements DailyQuoteToolUseCase {
private final List<DailyQuoteResponse> quotes = List.of(
new DailyQuoteResponse("성공은 매일 반복한 작은 노력들의 합이다.", "로버트 콜리어"),
new DailyQuoteResponse("시작이 반이다.", "아리스토텔레스"),
new DailyQuoteResponse("포기하지 않는 한 실패는 없다.", "알베르트 아인슈타인"),
new DailyQuoteResponse("가장 큰 위험은 위험 없는 삶이다.", "스티븐 코비")
);
@Override
public DailyQuoteResponse getDailyQuote(DailyQuoteRequest req) {
int index = new Random().nextInt(quotes.size());
DailyQuoteResponse selected = quotes.get(index);
log.info("[DailyQuoteTool] 명언 제공 완료: {}", selected.author());
return selected;
}
}

View File

@@ -0,0 +1,49 @@
package io.shinhanlife.dap.mcc.biz.smp.usecase.impl;
import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateRequest;
import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateResponse;
import io.shinhanlife.dap.mcc.biz.smp.usecase.ExchangeRateToolUseCase;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
/**
* @package io.shinhanlife.dap.mcc.service
* @className ExchangeRateToolService
* @description 실시간 환율 조회 툴
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Slf4j
@Service
public class ExchangeRateToolUseCaseImpl implements ExchangeRateToolUseCase {
private final RestClient restClient;
public ExchangeRateToolUseCaseImpl() {
this.restClient = RestClient.create();
}
@Override
public ExchangeRateResponse getExchangeRate(ExchangeRateRequest req) {
String targetCurrency = req.getCurrencyCode() != null ? req.getCurrencyCode().toUpperCase().trim() : "USD";
// 간단한 모의 데이터로 반환 (실제 구현 시 외부 연동)
double dummyRate = 1350.50;
if (targetCurrency.contains("JPY")) {
dummyRate = 905.20;
} else if (targetCurrency.contains("EUR")) {
dummyRate = 1450.30;
}
log.info("[ExchangeRateTool] 환율 조회 완료: {} -> {}", targetCurrency, dummyRate);
return new ExchangeRateResponse("KRW", targetCurrency, dummyRate);
}
}

View File

@@ -0,0 +1,53 @@
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 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 implements TeamMemberUseCase {
@Override
public Object getTeamMember(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,104 @@
package io.shinhanlife.dap.mcc.biz.smp.usecase.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.shinhanlife.dap.mcc.biz.smp.usecase.WeatherToolUseCase;
import io.shinhanlife.dap.mcc.biz.smp.dto.WeatherRequest;
import io.shinhanlife.dap.mcc.biz.smp.dto.WeatherResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @package io.shinhanlife.dap.mcc.service
* @className WeatherToolService
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.07.14
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.07.14 0986406 최초생성
*
* </pre>
*/
@Slf4j
@Service
public class WeatherToolUseCaseImpl implements WeatherToolUseCase {
private final RestClient restClient;
public WeatherToolUseCaseImpl() {
this.restClient = RestClient.create();
}
public WeatherResponse getWeather(WeatherRequest req) {
String city = req.city() != null ? req.city().trim() : "서울";
// 지역별 위경도 매핑 (간단한 예시)
double lat = 37.566;
double lon = 126.978;
if (city.contains("부산")) {
lat = 35.179;
lon = 129.075;
} else if (city.contains("제주")) {
lat = 33.499;
lon = 126.531;
} else if (city.contains("인천")) {
lat = 37.456;
lon = 126.705;
}
try {
String newRequestId = java.util.UUID.randomUUID().toString();
String url = String.format("https://api.open-meteo.com/v1/forecast?latitude=%f&longitude=%f&current_weather=true", lat, lon);
log.info("[WeatherTool] OUTBOUND HTTP IN - request-id: {}", newRequestId);
log.info("[WeatherTool] 날씨 조회 요청 URL: {}", url);
String responseStr = restClient.get()
.uri(url)
.header("request-id", newRequestId)
.retrieve()
.body(String.class);
log.info("[WeatherTool] OUTBOUND HTTP OUT - request-id: {}", newRequestId);
ObjectMapper mapper = new ObjectMapper();
JsonNode response = mapper.readTree(responseStr);
if (response != null && response.has("current_weather")) {
JsonNode current = response.get("current_weather");
double temp = current.path("temperature").asDouble();
double windSpeed = current.path("windspeed").asDouble();
String time = current.path("time").asText();
int weatherCode = current.path("weathercode").asInt();
String summary = parseWeatherCode(weatherCode);
String formattedTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
return new WeatherResponse(city, temp, windSpeed, formattedTime, summary);
}
} catch (Exception e) {
log.error("[WeatherTool] 날씨 API 연동 실패: {}", e.getMessage());
return new WeatherResponse(city, 0.0, 0.0, "", "날씨 정보를 불러오는데 실패했습니다.");
}
return new WeatherResponse(city, 0.0, 0.0, "", "알 수 없는 응답입니다.");
}
private String parseWeatherCode(int code) {
if (code == 0) return "맑음 (Clear)";
if (code >= 1 && code <= 3) return "구름조금/흐림 (Cloudy)";
if (code >= 45 && code <= 48) return "안개 (Fog)";
if (code >= 51 && code <= 67) return "비/이슬비 (Rain)";
if (code >= 71 && code <= 77) return "눈 (Snow)";
if (code >= 95) return "뇌우/천둥번개 (Thunderstorm)";
return "알 수 없음 (Unknown)";
}
}

View File

@@ -0,0 +1,31 @@
package io.shinhanlife.dap.mcc.biz.sol.converter;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailResponse;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_O;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
/**
* @package io.shinhanlife.dap.mcc.biz.sol.converter
* @className SolReqDetailConverter
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Mapper(componentModel = "spring")
public interface SolReqDetailConverter {
@Mapping(source = "srId", target = "srId")
SOLG00000002_I toLegacyRequest(SolReqDetailRequest req);
SolReqDetailResponse toResponse(SOLG00000002_O mciRes);
}

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,33 @@
package io.shinhanlife.dap.mcc.biz.sol.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springaicommunity.mcp.annotation.McpToolParam;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.biz.sol.dto
* @className SolReqDetailRequest
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SolReqDetailRequest {
@McpToolParam(description = "상세 조회할 SOL 의뢰서 ID", required = true)
@Schema(example = "SR20260805")
private String srId;
}

View File

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

View File

@@ -0,0 +1,39 @@
package io.shinhanlife.dap.mcc.biz.sol.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springaicommunity.mcp.annotation.McpToolParam;
import com.fasterxml.jackson.annotation.JsonInclude;
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 {
@McpToolParam(description = "진행상태 (예: 진행중, 완료 등)", required = false)
@Schema(example = "RECEIVED")
private String status;
@McpToolParam(description = "조회기간 (예: 1개월, 3개월 등)", required = false)
private String period;
@McpToolParam(description = "조회대상 (예: 나의 업무, 전체 등)", required = false)
@Schema(example = "USER")
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,42 @@
package io.shinhanlife.dap.mcc.biz.sol.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest;
/**
* @package io.shinhanlife.dap.mcc.biz.sol.usecase
* @className SolReqDetailUseCase
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
public interface SolReqDetailUseCase {
@McpTool(name = "sol_request_detail", title = "SolReqDetail 툴", description = "SOL 의뢰서 상세 조회", annotations = @McpTool.McpAnnotations(openWorldHint = true, readOnlyHint = true))
@GrowToolHint(
requiresApproval = false,
categoryKey = "sol",
mappingId = "SOLG00000002",
displayDescription = "SOL 의뢰서 한 건의 상세 정보를 조회합니다.",
functionDescription = "SOL 의뢰서 식별자를 기준으로 의뢰서 상세 내용을 조회한다.",
whenToUse = "사용자가 특정 SOL 의뢰서의 상세 내용과 진행 정보를 확인하려는 경우 사용한다.",
whenNotToUse = "의뢰서 목록만 찾거나 의뢰서를 변경 또는 처리하려는 경우에는 사용하지 않는다.",
ioLimits = "정확한 의뢰서 ID가 필요하며 등록된 상세 정보만 반환한다.",
exampleQueries = {"이 SOL 의뢰서 상세를 보여줘", "의뢰서 ID로 처리 내용을 확인해줘", "선택한 의뢰서의 상세 정보를 알려줘"},
destructive = false,
idempotent = true,
tags = {"SOL", "의뢰서"},
requiredEnvKeys = {},
ownerOrg = "MCP_TOOL"
)
Object getSolRequestDetail(SolReqDetailRequest req);
}

View File

@@ -0,0 +1,12 @@
package io.shinhanlife.dap.mcc.biz.sol.usecase;
import org.springaicommunity.mcp.annotation.McpTool;
import io.shinhanlife.dap.lib.annotation.GrowToolHint;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest;
public interface SolReqListUseCase {
@McpTool(name = "sol_request_list", title = "SolReqList 툴", description = "SOL 의뢰서 목록 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true))
@GrowToolHint(requiresApproval = false, categoryKey = "sol", mappingId = "SOLG00000001")
Object searchSolRequests(SolReqListRequest req);
}

View File

@@ -0,0 +1,101 @@
package io.shinhanlife.dap.mcc.biz.sol.usecase.impl;
import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqDetailConverter;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest;
import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailResponse;
import io.shinhanlife.dap.mcc.biz.sol.usecase.SolReqDetailUseCase;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_O;
import io.shinhanlife.glow.communication.dto.Transfer;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* @package io.shinhanlife.dap.mcc.biz.sol.usecase.impl
* @className SolReqDetailUseCaseImpl
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SolReqDetailUseCaseImpl implements SolReqDetailUseCase {
private final MciNclgClient mci;
private final SolReqDetailConverter converter;
@Value("${sol.req-detail.mock-enabled:false}")
private boolean mockEnabled;
@Override
public Object getSolRequestDetail(SolReqDetailRequest req) {
log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "solReqDetail", req);
if (req == null || req.getSrId() == null || req.getSrId().isBlank()) {
return Map.of("status", "ERROR", "message", "srId는 필수입니다.");
}
if (mockEnabled) {
return createLocalSampleResponse(req.getSrId());
}
try {
SOLG00000002_I mciRequest = converter.toLegacyRequest(req);
Transfer<SOLG00000002_O> mciResponse = mci.callTo(
"SOLG00000002", "SOLG00000002", mciRequest, SOLG00000002_O.class);
if (mciResponse == null || mciResponse.getBody() == null) {
return Map.of("status", "NOT_FOUND", "message", "의뢰서 상세 정보를 찾을 수 없습니다.");
}
return converter.toResponse(mciResponse.getBody());
} catch (Exception e) {
log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e);
return Map.of(
"status", "ERROR",
"message", e.getMessage() != null ? e.getMessage() : "Unknown error");
}
}
private Object createLocalSampleResponse(String srId) {
SolReqDetailResponse response = new SolReqDetailResponse();
if ("SR-2026-001".equalsIgnoreCase(srId)) {
response.setSrId("SR-2026-001");
response.setSrName("AX HUB 메인 화면 UI 개편");
response.setProcess("진행중");
response.setDevStage("개발(단위테스트)");
response.setAppName("AX HUB");
response.setRequester("신한준");
response.setRequestDate("2026-07-01");
response.setDueDate("2026-08-31");
response.setDescription("AX HUB 메인 화면의 사용성과 접근성을 개선하는 UI 개편 의뢰입니다.");
return response;
}
if ("SR-2026-002".equalsIgnoreCase(srId)) {
response.setSrId("SR-2026-002");
response.setSrName("SOL 연동 모듈 추가 개발");
response.setProcess("진행중");
response.setDevStage("분석/설계");
response.setAppName("MCP Gateway");
response.setRequester("고석민");
response.setRequestDate("2026-07-15");
response.setDueDate("2026-09-30");
response.setDescription("SOL 의뢰서 조회 기능을 MCP 도구로 제공하기 위한 연동 모듈 개발 의뢰입니다.");
return response;
}
return Map.of(
"status", "NOT_FOUND",
"message", "의뢰서를 찾을 수 없습니다.",
"srId", srId);
}
}

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 searchSolRequests(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,33 @@
package io.shinhanlife.dap.mcc.cus;
/**
* @package io.shinhanlife.dap.mcc.cus
* @className DapWasCusApplication
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Import;
import io.shinhanlife.dap.lib.mcp.ToolMcpServerConfiguration;
@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"})
@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"})
@EnableCaching
@Import(ToolMcpServerConfiguration.class)
public class DapWasCusApplication {
public static void main(String[] args) {
SpringApplication.run(DapWasCusApplication.class, args);
}
}

View File

@@ -0,0 +1,17 @@
package io.shinhanlife.dap.mcc.infra.itrf.http.insurance;
import io.shinhanlife.dap.lib.integration.http.component.AxhubHttpComponent;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class InsuranceClient {
private static final String API_NAME = "insurance";
private final AxhubHttpComponent http;
public <I, O> O call(I request, Class<O> responseType) {
return http.call(API_NAME, request, responseType);
}
}

View File

@@ -0,0 +1,19 @@
package io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class InsuranceClaimProcessorHttpRequest {
@Schema(description = "보험 청구 번호", example = "CLM20230001", requiredMode = Schema.RequiredMode.REQUIRED)
private String claimNumber;
@Schema(description = "청구 금액", example = "1500000.00", requiredMode = Schema.RequiredMode.REQUIRED)
private Double claimAmount;
@Schema(description = "청구 일자 (YYYY-MM-DD)", example = "2023-09-15", requiredMode = Schema.RequiredMode.REQUIRED)
private String claimDate;
}

View File

@@ -0,0 +1,16 @@
package io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class InsuranceClaimProcessorHttpResponse {
private String resultCode;
private String resultMessage;
@Schema(description = "청구 처리 고유 식별자", example = "CLM20230001", requiredMode = Schema.RequiredMode.REQUIRED)
private String claimId;
}

View File

@@ -0,0 +1,72 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a;
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 io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;
import io.shinhanlife.glow.communication.dto.Transfer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io
* @className MciCfpaClient
* @description 보장분석결과조회 MCI 호출 클라이언트
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MciCfpaClient {
private final AxhubMciComponent mci;
// 인터페이스 코드 상수
private static final String INTERFACE_CODE = "CLCNNB00001"; // CLCCFP00001 (Onnba용 코드 유지)
// 정상 응답 코드 상수
private static final String SUCCESS_CODE_COM = "COM00139";
private static final String SUCCESS_CODE_CFP = "CFP00000";
private static final String NO_DATA_CODE = "COM00150";
// 예외 코드 상수
private static final String ERROR_CODE_PROCESS = "CLC00007";
private static final String ERROR_CODE_MESSAGE = "CLC00043";
/**
* 보장분석결과조회
*
* @param inDto 입력 DTO
* @return 출력 객체
*/
public Object callCfpa0001(CLCNNB00001_I mciReq) throws Exception {
// 인터페이스 IO 객체 생성 및 매핑
// 인터페이스 호출
Transfer<CLCNNB00001_O> resTransfer = mci.callTo(INTERFACE_CODE, mciReq, CLCNNB00001_O.class);
// 응답 검증
validateResponse(resTransfer);
// 메시지 검증
validateMessage(String.valueOf(resTransfer.getMessage()));
return resTransfer.getBody();
}
private void validateResponse(Transfer<?> resTransfer) {
log.info("응답 검증 로직 수행");
}
private void validateMessage(String message) {
log.info("메시지 검증 로직 수행: {}", message);
}
}

View File

@@ -0,0 +1,91 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io
* @className CLCNNB00001_I
* @description 보장분석결과조회 MCI 요청 전문
* @author 0986406
* @create 2026.09.01
*/
@Data
public class CLCNNB00001_I {
@JsonProperty("unfcPrbuIrcoAddu")
private UnfcPrbuIrcoAdduDto unfcPrbuIrcoAddu;
@JsonProperty("dalScCd")
private String dalScCd;
@JsonProperty("cstSucoRltyCd")
private String cstSucoRltyCd;
@JsonProperty("csNo")
private String csNo;
@JsonProperty("rdreNo")
private String rdreNo;
@JsonProperty("unfcPvsCalReqYn")
private String unfcPvsCalReqYn;
@JsonProperty("kcisPymmTnnrRequest")
private String kcisPymmTnnrRequest;
@JsonProperty("lmovYn")
private String lmovYn;
@JsonProperty("genPsthApvTrgtYn")
private String genPsthApvTrgtYn;
@JsonProperty("ircoLmovEcpbTrgtYn")
private String ircoLmovEcpbTrgtYn;
@JsonProperty("digCalYn")
private String digCalYn;
@JsonProperty("prbuIciDigCalYn")
private String prbuIciDigCalYn;
@JsonProperty("sucoIspaBasDto")
private SucoIspaBasDto sucoIspaBasDto;
@Data
public static class UnfcPrbuIrcoAdduDto {
}
@Data
public static class SucoIspaBasDto {
@JsonProperty("ccnDalTypCd")
private String ccnDalTypCd;
@JsonProperty("nwcnptCursCd")
private String nwcnptCursCd;
@JsonProperty("induAsctScCd")
private String induAsctScCd;
@JsonProperty("cepeOgnzNo")
private String cepeOgnzNo;
@JsonProperty("cepePrafNo")
private String cepePrafNo;
@JsonProperty("clmoOgnzNo")
private String clmoOgnzNo;
@JsonProperty("clmoPrafNo")
private String clmoPrafNo;
@JsonProperty("sucoYmd")
private String sucoYmd;
@JsonProperty("ispDt")
private String ispDt;
@JsonProperty("contYmd")
private String contYmd;
}
}

View File

@@ -0,0 +1,16 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io;
import lombok.Data;
/**
* @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io
* @className CLCNNB00001_O
* @description 보장분석결과조회 MCI 응답 전문
* @author 0986406
* @create 2026.09.01
*/
@Data
public class CLCNNB00001_O {
// 응답 전문 필드 정의 (필요에 따라 추가)
private Object resultData;
}

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,23 @@
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 SOLG00000002_I
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Data
public class SOLG00000002_I {
private String srId;
}

View File

@@ -0,0 +1,31 @@
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 SOLG00000002_O
* @description AX HUB 시스템 처리 클래스
* @author 0986406
* @create 2026.09.01
* <pre>
* ---------- 개정이력 ----------
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2026.09.01 0986406 최초생성
*
* </pre>
*/
@Data
public class SOLG00000002_O {
private String srId;
private String srName;
private String process;
private String devStage;
private String appName;
private String requester;
private String requestDate;
private String dueDate;
private String description;
}

View File

@@ -0,0 +1,68 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.ncm.d.io;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.shinhanlife.glow.communication.annotation.GlowTrgmField;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* ONCMD0030_O 매핑 DTO
*/
@NoArgsConstructor
@Getter
@Setter
public class ONCMD0030_O {
@GlowTrgmField(order = 1, description = "고객스마트정보조회OutDto", type = "gm")
private List<CstSmartIfinOutDto> cstSmartIfinOutDto;
@GlowTrgmField(order = 1, description = "고객스마트정보조회OutDto2", type = "gm")
private List<CstSmartIfinOutDto2> cstSmartIfinOutDto2;
@JsonIgnoreProperties(ignoreUnknown = true)
@NoArgsConstructor
@Getter
@Setter
public static class CstSmartIfinOutDto {
@GlowTrgmField(order = 1, length = 1, description = "동의여부")
private String agrYn;
@GlowTrgmField(order = 2, length = 12, description = "고객번호")
private String csNo;
@GlowTrgmField(order = 3, length = 50, description = "주민등록번호")
private String rdreNo;
@GlowTrgmField(order = 4, length = 20, description = "등록일시")
private String rgiDt;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@NoArgsConstructor
@Getter
@Setter
public static class CstSmartIfinOutDto2 {
@GlowTrgmField(order = 1, length = 1, description = "동의여부")
private String agrYnaa;
@GlowTrgmField(order = 2, length = 12, description = "고객번호")
private String csNoaa;
@GlowTrgmField(order = 3, length = 50, description = "주민등록번호")
private String rdreNoaa;
@GlowTrgmField(order = 4, length = 20, description = "등록일시")
private String rgiDtaa;
}
}

View File

@@ -0,0 +1,49 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d;
import io.shinhanlife.dap.mcc.biz.cmm.dto.CustomerGuidanceToolResponse;
import io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io.ONILD0320_I;
import io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io.ONILD0320_O;
import io.shinhanlife.glow.BizException;
import io.shinhanlife.glow.GlowLogger;
import lombok.extern.slf4j.Slf4j;
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.nild
* @className MciNildClient
* @description AX HUB ?쒖뒪??泥섎━ ?대옒??
* @author Boram
* @create 2026.08.12
* <pre>
* ---------- 媛쒖젙?대젰 ----------
* ?섏젙?? ?섏젙?? ?섏젙?댁슜
* ---------- -------- ---------------------------
* 2026.08.12 Boram 理쒖큹?앹꽦
*
* </pre>
*/
@Component
@RequiredArgsConstructor
public class MciNildClient {
private final AxhubMciComponent mci;
public ONILD0320_O callOnild0320(ONILD0320_I onild0320_i){
try {
Transfer<ONILD0320_O> resTransfer = mci.callTo(
"CTMNILO00007",
null,
onild0320_i,
ONILD0320_O.class
);
ONILD0320_O onild0320_o = resTransfer.getBody();
return onild0320_o;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,41 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.shinhanlife.glow.GlowMciFieldInfo;
import io.shinhanlife.glow.communication.annotation.GlowTrgmField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Data
@Getter
@Setter
@NoArgsConstructor
public class ONILD0320_I {
@GlowTrgmField(order = 1, description = "고객 통합 안내이력 조회", type="gs")
private CstUnfcNotiCarhInqrInDto cstUnfcNotiCarhInqrInDto;
@NoArgsConstructor
@Getter
@Setter
public static class CstUnfcNotiCarhInqrInDto {
@GlowMciFieldInfo(order = 1, length = 5, description = "고객번호")
private String csNo;
@GlowMciFieldInfo(order = 2, length = 5, description = "안내장코드")
private String ntleCd;
@GlowMciFieldInfo(order = 3, length = 5, description = "안내발송방법코드")
private String notiPmlMdCd;
@GlowMciFieldInfo(order = 4, length = 5, description = "조회시작일자")
private String inqrStrtYmd;
@GlowMciFieldInfo(order = 5, length = 5, description = "조회종료일자")
private String inqrEndYmd;
}
}

View File

@@ -0,0 +1,109 @@
package io.shinhanlife.dap.mcc.infra.itrf.mci.nil.d.io;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.shinhanlife.glow.GlowMciFieldInfo;
import io.shinhanlife.glow.communication.annotation.GlowTrgmField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.List;
@NoArgsConstructor
@Getter
@Setter
public class ONILD0320_O {
@GlowTrgmField(order = 1, description = "고객 통합 안내이력 조회out", type="gm")
private List<CstUnfcNotiCarhInqrOutDto> cstUnfcNotiCarhInqrOutDto;
@NoArgsConstructor
@Getter
@Setter
public static class CstUnfcNotiCarhInqrOutDto {
@GlowMciFieldInfo(order = 1, length = 5, description = "고객번호")
private String csNo;
@GlowMciFieldInfo(order = 2, length = 5, description = "안내일시")
private String notiDt;
@GlowMciFieldInfo(order = 3, length = 5, description = "발송일련번호")
private int pmlSriaNo;
@GlowMciFieldInfo(order = 4, length = 5, description = "안내신청상세일시")
private String notiPetDtptDt;
@GlowMciFieldInfo(order = 5, length = 5, description = "안내장코드")
private String ntleCd;
@GlowMciFieldInfo(order = 6, length = 5, description = "안내장명")
private String ntleNm;
@GlowMciFieldInfo(order = 7, length = 5, description = "안내발송일련번호")
private int notiPmlSriaNo;
@GlowMciFieldInfo(order = 8, length = 5, description = "안내발송방법코드")
private String notiPmlMdCd;
@GlowMciFieldInfo(order = 9, length = 5, description = "안내수신정보암호화내용")
private String notiRcvInfoEncrCt;
@GlowMciFieldInfo(order = 10, length = 5, description = "메시지ID")
private String msgId;
@GlowMciFieldInfo(order = 11, length = 5, description = "안내내용")
private String notiCt;
@GlowMciFieldInfo(order = 12, length = 5, description = "통합안내발송결과코드")
private String unfcNotiPmlRsltCd;
@GlowMciFieldInfo(order = 13, length = 5, description = "통합안내발송결과상세코드")
private String unfcNotiPmlRsltDtptCd;
@GlowMciFieldInfo(order = 14, length = 5, description = "발송시스템코드")
private String pmlSystCd;
@GlowMciFieldInfo(order = 15, length = 5, description = "발송조직번호")
private String pmlOgnzNo;
@GlowMciFieldInfo(order = 16, length = 5, description = "발송조직명")
private String pmlOgnzNm;
@GlowMciFieldInfo(order = 17, length = 5, description = "발신기관조직명")
private String msdilttOgnzNm;
@GlowMciFieldInfo(order = 18, length = 5, description = "조회구분명")
private String inqrDvsnNm;
@GlowMciFieldInfo(order = 19, length = 5, description = "등록일시")
private String rgiDt;
@GlowMciFieldInfo(order = 20, length = 5, description = "등록자명")
private String rgstNm;
@GlowMciFieldInfo(order = 21, length = 5, description = "등록자조직명")
private String rgstOgnzNm;
@GlowMciFieldInfo(order = 22, length = 5, description = "최종변경자명")
private String lstPrwpNm;
@GlowMciFieldInfo(order = 23, length = 5, description = "최종변경일시")
private String lstChgDt;
@GlowMciFieldInfo(order = 24, length = 5, description = "최종변경자조직명")
private String lstPrwpOgnzNm;
@GlowMciFieldInfo(order = 25, length = 5, description = "원천회사구분코드")
private String oriCpnyDvsnCd;
@GlowMciFieldInfo(order = 26, length = 5, description = "고객한글명")
private String cstHanNm;
@GlowMciFieldInfo(order = 27, length = 5, description = "시스템등록인사번호")
private String systRgiPrafNo;
@GlowMciFieldInfo(order = 28, length = 5, description = "시스템변경인사번호")
private String systChgPrafNo;
}
}

View File

@@ -0,0 +1,424 @@
package io.shinhanlife.dap.mcc.presentation;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Pattern;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.RegexPatternTypeFilter;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* MCI DTO 클래스를 조회하고 인터페이스 설계서 형식의 엑셀 파일을 생성한다.
* 외부 템플릿 파일에 의존하지 않고 Apache POI로 양식과 데이터를 모두 만든다.
*/
@RestController
public class DtoExcelDownloadController {
private static final int FIRST_FIELD_ROW = 12;
private static final int TEMPLATE_LAST_ROW = 35;
private static final int COLUMN_COUNT = 20;
private static final MediaType XLSX_MEDIA_TYPE = MediaType.parseMediaType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
private static final String MCI_BASE_PACKAGE = "io.shinhanlife.dap.mcc.infra.itrf.mci";
private static final String INVALID_DTO_MESSAGE =
"dto의 내용이 엑셀파일 양식에 맞지않습니다 파일을 확인해주세요";
private final Map<String, String> dtoClasses;
public DtoExcelDownloadController() {
this.dtoClasses = scanDtoClasses();
}
@GetMapping("/dto-download/options")
public List<String> options() {
// 클래스패스에서 자동 검색된 DTO 목록을 화면에 제공한다.
return List.copyOf(dtoClasses.keySet());
}
@GetMapping("/dto-download/{dtoName}")
public ResponseEntity<byte[]> download(@PathVariable String dtoName) throws IOException {
// 스캔되지 않은 이름을 받아 임의 클래스를 조회하지 못하도록 제한한다.
String className = dtoClasses.get(dtoName);
if (className == null) {
return ResponseEntity.notFound().build();
}
byte[] workbook = createWorkbook(dtoName, className);
String fileName = dtoName + ".xlsx";
return ResponseEntity.ok()
.contentType(XLSX_MEDIA_TYPE)
.contentLength(workbook.length)
.header(HttpHeaders.CONTENT_DISPOSITION,
ContentDisposition.attachment().filename(fileName).build().toString())
.body(workbook);
}
@ExceptionHandler(DtoFormatException.class)
public ResponseEntity<String> handleInvalidDto() {
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY)
.contentType(MediaType.parseMediaType("text/plain;charset=UTF-8"))
.body(INVALID_DTO_MESSAGE);
}
private byte[] createWorkbook(String dtoName, String className) throws IOException {
// 요청마다 새 워크북을 생성하므로 여러 사용자의 다운로드가 서로 영향을 주지 않는다.
try (XSSFWorkbook workbook = createTemplateWorkbook();
ByteArrayOutputStream output = new ByteArrayOutputStream()) {
Sheet sheet = workbook.getSheetAt(0);
setText(sheet, 2, 2, dtoName);
setText(sheet, 3, 2, dtoName);
List<FieldRow> fields;
try {
fields = describeFields(Class.forName(className));
} catch (ReflectiveOperationException error) {
throw new IOException("DTO class could not be inspected: " + className, error);
}
writeFields(sheet, fields);
workbook.write(output);
return output.toByteArray();
}
}
private XSSFWorkbook createTemplateWorkbook() {
// 기준 문서의 시트명, 열 너비, 병합, 색상과 테두리를 코드로 재현한다.
XSSFWorkbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("대내");
sheet.setDisplayGridlines(false);
sheet.createFreezePane(0, 12);
sheet.getPrintSetup().setLandscape(true);
sheet.setRepeatingRows(new CellRangeAddress(11, 11, -1, -1));
double[] widths = {4.44, 8, 23.22, 23.22, 10, 14, 18, 11, 9, 7.44,
8, 9.55, 9, 10.55, 11.44, 9, 13, 10.55, 14, 30};
for (int column = 0; column < widths.length; column++) {
sheet.setColumnWidth(column, (int) (widths[column] * 256));
}
CellStyle titleStyle = style(workbook, "000000", "FFFFFF", true, (short) 14,
HorizontalAlignment.CENTER, false);
CellStyle sectionStyle = style(workbook, "F2F2F2", "000000", true, (short) 10,
HorizontalAlignment.CENTER, false);
CellStyle labelStyle = borderedStyle(workbook, "F2F2F2", true, HorizontalAlignment.CENTER);
CellStyle inputStyle = borderedStyle(workbook, "C6D9F1", false, HorizontalAlignment.LEFT);
CellStyle requiredStyle = borderedStyle(workbook, "C6D9F1", false, HorizontalAlignment.LEFT);
CellStyle autoStyle = borderedStyle(workbook, "F2DCDB", false, HorizontalAlignment.LEFT);
CellStyle userStyle = borderedStyle(workbook, "FFFFFF", false, HorizontalAlignment.LEFT);
CellStyle headerStyle = borderedStyle(workbook, "D9D9D9", true, HorizontalAlignment.CENTER);
headerStyle.setWrapText(true);
CellStyle whiteDataStyle = borderedStyle(workbook, "FFFFFF", false, HorizontalAlignment.CENTER);
CellStyle blueDataStyle = borderedStyle(workbook, "C6D9F1", false, HorizontalAlignment.CENTER);
CellStyle pinkDataStyle = borderedStyle(workbook, "F2DCDB", false, HorizontalAlignment.CENTER);
createStyledRow(sheet, 0, 25.5f, titleStyle);
merge(sheet, "A1:T1");
setText(sheet, 0, 0, "인터페이스 설계서(대내)");
createStyledRow(sheet, 1, 18f, sectionStyle);
merge(sheet, "A2:T2");
setText(sheet, 1, 0, "기본정보");
String[] labels = {"코드", "한글명", "영문명", "암호화", "유형", "레코드구분자", "필드구분자"};
for (int index = 0; index < labels.length; index++) {
int rowIndex = index + 2;
Row row = sheet.createRow(rowIndex);
cell(row, 1, labelStyle).setCellValue(labels[index]);
cell(row, 2, inputStyle);
cell(row, 3, inputStyle);
merge(sheet, "C" + (rowIndex + 1) + ":D" + (rowIndex + 1));
}
setText(sheet, 6, 2, "json");
sheet.getRow(7).setHeightInPoints(24);
sheet.getRow(8).setHeightInPoints(24);
for (int rowIndex = 3; rowIndex <= 5; rowIndex++) {
Row row = sheet.getRow(rowIndex);
CellStyle legendStyle = rowIndex == 3 ? requiredStyle : rowIndex == 4 ? autoStyle : userStyle;
cell(row, 5, legendStyle);
cell(row, 6, legendStyle);
merge(sheet, "F" + (rowIndex + 1) + ":G" + (rowIndex + 1));
}
setText(sheet, 3, 7, "필수입력");
setText(sheet, 4, 7, "필드자동채우기(메타시스템 연동시)");
setText(sheet, 5, 7, "사용자입력(필요시)");
createStyledRow(sheet, 10, 18f, sectionStyle);
merge(sheet, "A11:T11");
setText(sheet, 10, 0, "필드정보");
String[] headers = {"NO", "Level", "한글명", "부모식별자(한글명)", "끝수여부", "영문명",
"부모식별자(영문명)", "데이터유형", "필드길이", "SCALE", "기본값", "정렬기준",
"채움문자", "암호화방식", "메타체크여부", "한글여부", "소수점포함여부",
"마스킹여부", "마스킹패턴코드", "비고"};
Row header = sheet.createRow(11);
header.setHeightInPoints(30);
for (int column = 0; column < headers.length; column++) {
cell(header, column, headerStyle).setCellValue(headers[column]);
}
for (int rowIndex = FIRST_FIELD_ROW; rowIndex <= TEMPLATE_LAST_ROW; rowIndex++) {
Row row = sheet.createRow(rowIndex);
row.setHeightInPoints(15.75f);
for (int column = 0; column < COLUMN_COUNT; column++) {
CellStyle dataStyle;
if (column == 0 || column == 4 || (column >= 14 && column <= 16) || column == 19) {
dataStyle = whiteDataStyle;
} else if (column >= 1 && column <= 3) {
dataStyle = blueDataStyle;
} else {
dataStyle = pinkDataStyle;
}
cell(row, column, dataStyle);
}
}
return workbook;
}
private CellStyle style(XSSFWorkbook workbook, String fillColor, String fontColor,
boolean bold, short fontSize, HorizontalAlignment alignment,
boolean bordered) {
CellStyle style = workbook.createCellStyle();
style.setAlignment(alignment);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setFillForegroundColor(new XSSFColor(java.awt.Color.decode("#" + fillColor), null));
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
Font font = workbook.createFont();
font.setFontName("맑은 고딕");
font.setFontHeightInPoints(fontSize);
font.setBold(bold);
font.setColor("FFFFFF".equals(fontColor) ? IndexedColors.WHITE.getIndex() : IndexedColors.BLACK.getIndex());
style.setFont(font);
if (bordered) setBorders(style);
return style;
}
private CellStyle borderedStyle(XSSFWorkbook workbook, String fillColor,
boolean bold, HorizontalAlignment alignment) {
return style(workbook, fillColor, "000000", bold, (short) 9, alignment, true);
}
private void setBorders(CellStyle style) {
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
}
private void createStyledRow(Sheet sheet, int rowIndex, float height, CellStyle style) {
Row row = sheet.createRow(rowIndex);
row.setHeightInPoints(height);
for (int column = 0; column < COLUMN_COUNT; column++) cell(row, column, style);
}
private Cell cell(Row row, int column, CellStyle style) {
Cell cell = row.createCell(column);
cell.setCellStyle(style);
return cell;
}
private void merge(Sheet sheet, String range) {
sheet.addMergedRegion(CellRangeAddress.valueOf(range));
}
private void writeFields(Sheet sheet, List<FieldRow> fields) {
// 기본 24행을 유지하고 필드가 더 많으면 마지막 행의 서식을 복제해 확장한다.
int requiredRows = Math.max(fields.size(), TEMPLATE_LAST_ROW - FIRST_FIELD_ROW + 1);
for (int offset = 0; offset < requiredRows; offset++) {
int rowIndex = FIRST_FIELD_ROW + offset;
Row row = sheet.getRow(rowIndex);
if (row == null) {
row = cloneTemplateRow(sheet, rowIndex);
}
clearRowValues(row);
setNumber(row, 0, offset + 1);
if (offset < fields.size()) {
FieldRow field = fields.get(offset);
setNumber(row, 1, field.level());
setText(row, 2, field.description());
setText(row, 3, field.parentDescription());
setText(row, 5, field.name());
setText(row, 6, field.parentName());
setText(row, 7, field.dataType());
if (field.length() > 0) {
setNumber(row, 8, field.length());
}
}
}
}
private Row cloneTemplateRow(Sheet sheet, int rowIndex) {
Row source = sheet.getRow(TEMPLATE_LAST_ROW);
Row target = sheet.createRow(rowIndex);
target.setHeight(source.getHeight());
for (int column = 0; column < COLUMN_COUNT; column++) {
Cell sourceCell = source.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
Cell targetCell = target.createCell(column);
CellStyle style = sourceCell.getCellStyle();
targetCell.setCellStyle(style);
}
return target;
}
private void clearRowValues(Row row) {
for (int column = 0; column < COLUMN_COUNT; column++) {
Cell cell = row.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
cell.setBlank();
}
}
private List<FieldRow> describeFields(Class<?> rootClass) {
List<FieldRow> output = new ArrayList<>();
appendFields(rootClass, 1, "", "", output);
return output;
}
private void appendFields(Class<?> type, int level, String parentName,
String parentDescription, List<FieldRow> output) {
// 중첩 DTO와 List 요소 타입을 재귀적으로 펼쳐 Level 및 부모 식별자를 계산한다.
List<Field> fields = new ArrayList<>(List.of(type.getDeclaredFields()));
fields.removeIf(field -> field.isSynthetic());
fields.sort(Comparator.comparingInt(this::fieldOrder));
for (Field field : fields) {
Annotation metadata = telegramMetadata(field);
// 한글명, 순서, 길이를 알 수 없는 DTO는 설계서 양식으로 변환할 수 없다.
if (metadata == null) {
throw new DtoFormatException();
}
String description = annotationString(metadata, "description", field.getName());
int length = annotationInt(metadata, "length", 0);
Class<?> nestedType = nestedType(field);
String dataType = annotationString(metadata, "type", simpleDataType(field));
output.add(new FieldRow(level, description, parentDescription, field.getName(),
parentName, dataType, length));
if (nestedType != null && nestedType != type) {
appendFields(nestedType, level + 1, field.getName(), description, output);
}
}
}
private int fieldOrder(Field field) {
return annotationInt(telegramMetadata(field), "order", Integer.MAX_VALUE);
}
private Annotation telegramMetadata(Field field) {
for (Annotation annotation : field.getDeclaredAnnotations()) {
if (annotation.annotationType().getSimpleName().equals("GlowTrgmField")) {
return annotation;
}
}
return null;
}
private String annotationString(Annotation annotation, String methodName, String fallback) {
Object value = annotationValue(annotation, methodName);
return value instanceof String text && !text.isBlank() ? text : fallback;
}
private int annotationInt(Annotation annotation, String methodName, int fallback) {
Object value = annotationValue(annotation, methodName);
return value instanceof Number number ? number.intValue() : fallback;
}
private Object annotationValue(Annotation annotation, String methodName) {
if (annotation == null) {
return null;
}
try {
Method method = annotation.annotationType().getMethod(methodName);
return method.invoke(annotation);
} catch (ReflectiveOperationException ignored) {
return null;
}
}
private Class<?> nestedType(Field field) {
Class<?> type = field.getType();
if (List.class.isAssignableFrom(type) && field.getGenericType() instanceof ParameterizedType generic) {
Type argument = generic.getActualTypeArguments()[0];
if (argument instanceof Class<?> itemType && isDtoType(itemType)) {
return itemType;
}
}
return isDtoType(type) ? type : null;
}
private boolean isDtoType(Class<?> type) {
return !type.isPrimitive()
&& !type.getName().startsWith("java.")
&& !type.isEnum();
}
private String simpleDataType(Field field) {
if (List.class.isAssignableFrom(field.getType())) {
return "List";
}
return field.getType().getSimpleName();
}
private void setText(Sheet sheet, int row, int column, String value) {
setText(sheet.getRow(row), column, value);
}
private void setText(Row row, int column, String value) {
row.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellValue(value == null ? "" : value);
}
private void setNumber(Row row, int column, int value) {
row.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellValue(value);
}
private Map<String, String> scanDtoClasses() {
// itrf.mci 하위의 모든 io 패키지를 검색하므로 신규 DTO 추가 시 하드코딩이 필요 없다.
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new RegexPatternTypeFilter(
Pattern.compile(".*\\.itrf\\.mci\\..*\\.io\\.[^.]+$")));
Map<String, String> classes = new TreeMap<>();
scanner.findCandidateComponents(MCI_BASE_PACKAGE).forEach(candidate -> {
String className = candidate.getBeanClassName();
if (className == null || className.contains("$")) {
return;
}
String simpleName = className.substring(className.lastIndexOf('.') + 1);
String previous = classes.putIfAbsent(simpleName, className);
if (previous != null) {
throw new IllegalStateException("Duplicate DTO class name: " + simpleName);
}
});
return Map.copyOf(classes);
}
private record FieldRow(int level, String description, String parentDescription,
String name, String parentName, String dataType, int length) {
}
private static final class DtoFormatException extends RuntimeException {
}
}

View File

@@ -0,0 +1,15 @@
# OCI 클라우드 개발 환경 전용 설정
server:
port: ${PORT:8084}
axhub:
gateway:
url: https://axhubmcp.devjun.net
spring:
config:
activate:
on-profile: dev
import:
- classpath:glow/application-glow.yml
- classpath:glow/application-glow-dev.yml

View File

@@ -0,0 +1,32 @@
# Local 환경 전용 설정 (H2 메모리 DB 등)
spring:
config:
activate:
on-profile: local
import:
- classpath:glow/application-glow.yml
- classpath:glow/application-glow-local.yml
datasource:
url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1;
driverClassName: com.p6spy.engine.spy.P6SpyDriver
username: sa
password: password
h2:
console:
enabled: true
mcp:
security:
tenant-domains:
mcp-client-1: CUSTOMER,COMMON
mcp-client-2: ALL
axhub:
gateway:
url: http://localhost:8081
tool:
url: ${AXHUB_TOOL_URL:http://localhost:${server.port}}
sol:
req-detail:
mock-enabled: true

View File

@@ -0,0 +1,16 @@
server:
port: ${PORT:8084}
spring:
config:
activate:
on-profile: prod
import:
- classpath:glow/application-glow.yml
- classpath:glow/application-glow-prod.yml
axhub:
gateway:
url: ${AXHUB_GATEWAY_URL}
tool:
url: ${AXHUB_TOOL_URL}

View File

@@ -0,0 +1,16 @@
server:
port: ${PORT:8084}
spring:
config:
activate:
on-profile: test
import:
- classpath:glow/application-glow.yml
- classpath:glow/application-glow-test.yml
axhub:
gateway:
url: ${AXHUB_GATEWAY_URL}
tool:
url: ${AXHUB_TOOL_URL}

View File

@@ -0,0 +1,19 @@
server:
port: 8084
spring:
application:
name: dap-was-cus
profiles:
active: local
logging:
level:
org.apache.kafka: ERROR
mcp:
namespace: ""
manifest:
bundle-id: was-cus
# Set the AA-assigned prefix before MCP pull activation (for example: cus.).
name-prefix: ""
security:
tenant-domains:
TESTER-DEV: ALL

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 1. 로그 패턴 설정 (MDC traceId 포함) -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n" />
<!-- 2. 콘솔(Console) 출력 설정 (로컬 개발용) -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 3. 파일(File) 출력 설정 (서버 운영용) -->
<!-- Logback에서 시스템 Hostname을 가져오기 위한 설정 -->
<property name="HOSTNAME" value="${HOSTNAME}" />
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/swlog/dap-was-cus/A01/${HOSTNAME}_A01.log</file> <!-- 현재 로그가 쌓이는 파일 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 매일 자정에 날짜를 붙여서 지난 로그를 분리 저장 -->
<fileNamePattern>/swlog/dap-was-cus/A01/${HOSTNAME}_A01_%d{yyyyMMdd}.%i.log</fileNamePattern>
<!-- 개별 로그 파일 크기를 100MB로 제한하고, 전체 보관 일수는 30일, 총 로그 누적 용량은 1GB로 제한 -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 4. 기본 로깅 레벨 설정 -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
<!-- 5. 우리 프로젝트 패키지는 디버그 레벨까지 상세히 보기 -->
<logger name="io.shinhanlife" level="DEBUG" />
</configuration>

View File

@@ -0,0 +1,4 @@
{
"resultCode" : "SUCCESS",
"data" : "[{\"date\":\"2024-01-05\",\"message\":\"안내 내용\"}]"
}

View File

@@ -0,0 +1,4 @@
{
"resultCode" : "SUCCESS",
"claimId" : "CLM20230001"
}

View File

@@ -0,0 +1,25 @@
name: cmm_comcode_lookup
display_name: 메타 공통코드 조회
version: 1.0.0
category_key: cmm
description:
function: 통합코드 그룹과 코드명 조건으로 메타 공통코드 목록을 조회한다.
when_to_use: 업무 코드의 값과 표시명을 확인하거나 유효한 코드 목록이 필요한 경우 사용한다.
when_not_to_use: 공통코드를 신규 등록하거나 변경 또는 삭제하려는 경우에는 사용하지 않는다.
io_limits: 검색 조건에 맞는 코드와 코드명만 반환하며 코드 데이터는 변경하지 않는다.
display_description: 메타 시스템의 공통코드 목록을 조회합니다.
example_queries: ["사용 상태 코드 목록을 알려줘", "고객 구분 공통코드를 찾아줘", "사용 중인 통합코드를 조회해줘"]
read_only: true
destructive: false
idempotent: true
parameters_schema:
type: object
properties:
groupCode: {type: string, description: 조회할 통합코드 그룹 ID}
codeName: {type: string, description: 코드명 검색 키워드}
useYn: {type: string, description: 사용 여부 Y 또는 N, enum: [Y, N]}
additionalProperties: false
tags: [메타, 공통코드]
legacy_interface_id: CLCNNB00001
required_env_keys: []
owner_org: MCP_TOOL

View File

@@ -0,0 +1,28 @@
name: cmm_customer_tool
display_name: 고객 통합 안내이력 조회
version: 1.0.0
category_key: cmm
description:
function: 고객의 통합 안내 이력을 조회하는 도구입니다.
when_to_use: 고객 ID와 조회 기간을 기반으로 해당 기간 동안의 안내 이력을 확인할 때 사용합니다.
when_not_to_use: 안내 이력을 생성하거나 수정할 때는 사용하지 않습니다.
io_limits: 안내 이력 조회만 수행하며 데이터를 변경하지 않습니다.
display_description: 고객의 통합 안내 이력을 조회합니다.
example_queries: ["고객 통합 안내이력 조회해줘", "특정 기간의 안내 이력을 확인해줘", "고객 번호로 안내 이력을 찾아줘"]
read_only: true
destructive: false
idempotent: true
parameters_schema:
type: object
properties:
csNo: {type: string, description: 고객번호}
ntleCd: {type: string, description: 안내장코드}
notiPmlMdCd: {type: string, description: 안내발송방법코드}
inqrStrYmd: {type: string, description: 조회시작일자}
inqrEndYmd: {type: string, description: 조회종료일자}
required: [csNo]
additionalProperties: false
tags: [고객, 통합안내이력]
legacy_interface_id: ONILD0320
required_env_keys: []
owner_org: MCP_TOOL

View File

@@ -0,0 +1,24 @@
name: cmm_meta_table
display_name: 메타 테이블 조회
version: 1.0.0
category_key: cmm
description:
function: 물리명, 논리명 또는 소유자 조건으로 메타 테이블 정보를 조회한다.
when_to_use: 사용자가 업무 데이터의 테이블명이나 소유 스키마를 확인하려는 경우 사용한다.
when_not_to_use: 테이블을 생성하거나 구조를 변경하려는 경우에는 사용하지 않는다.
io_limits: 메타에 등록된 테이블 설명 정보만 반환하며 실제 테이블 데이터는 조회하지 않는다.
display_description: 메타 시스템에 등록된 테이블 정보를 조회합니다.
example_queries: ["고객 기본 테이블을 찾아줘", "계약 관련 테이블 목록을 보여줘", "특정 스키마의 테이블을 조회해줘"]
read_only: true
destructive: false
idempotent: true
parameters_schema:
type: object
properties:
tableName: {type: string, description: 테이블 물리명 검색어}
tableLogicalName: {type: string, description: 테이블 논리명 검색어}
owner: {type: string, description: 테이블 소유 스키마명}
additionalProperties: false
tags: [메타, 테이블]
required_env_keys: []
owner_org: MCP_TOOL

View File

@@ -0,0 +1,23 @@
name: cmm_template_url
display_name: 업무 템플릿 다운로드 URL 조회
version: 1.0.0
category_key: cmm
description:
function: 요청한 업무 템플릿 파일을 내려받을 수 있는 URL을 반환한다.
when_to_use: 사용자가 엑셀이나 워드 업무 양식의 다운로드 위치를 요청한 경우 사용한다.
when_not_to_use: 템플릿 내용을 작성하거나 업로드 또는 변경하려는 경우에는 사용하지 않는다.
io_limits: 등록된 템플릿 ID에 대한 다운로드 URL만 반환하며 파일 자체는 반환하지 않는다.
display_description: 업무 템플릿을 다운로드할 수 있는 URL을 제공합니다.
example_queries: ["청구 양식 다운로드 링크를 알려줘", "업무용 엑셀 템플릿을 받고 싶어", "등록된 문서 양식 위치를 찾아줘"]
read_only: true
destructive: false
idempotent: true
parameters_schema:
type: object
properties:
templateId: {type: string, description: 다운로드할 템플릿 식별자}
required: [templateId]
additionalProperties: false
tags: [템플릿, 다운로드]
required_env_keys: []
owner_org: MCP_TOOL

View File

@@ -0,0 +1,26 @@
name: ins_insurance_processor
display_name: 보험금 청구 처리
version: 1.0.0
category_key: ins
description:
function: 보험금 청구번호, 청구금액과 청구일을 받아 청구 처리 요청을 수행한다.
when_to_use: 사용자가 확인된 보험금 청구 정보를 실제 처리계에 접수하려는 경우 사용한다.
when_not_to_use: 청구 상태만 조회하거나 필수 정보가 확인되지 않은 경우에는 사용하지 않는다.
io_limits: 실행 시 업무 상태가 변경될 수 있으므로 호출 전에 입력값과 사용자 의사를 확인해야 한다.
display_description: 확인된 보험금 청구 요청을 처리계에 전달합니다.
example_queries: ["확인한 내용으로 보험금 청구를 접수해줘", "이 청구번호의 보험금 처리를 진행해줘", "오늘 날짜로 보험금 청구 요청을 보내줘"]
read_only: false
destructive: true
idempotent: false
parameters_schema:
type: object
properties:
claimNumber: {type: string, description: 처리할 보험금 청구번호}
claimAmount: {type: number, description: 처리할 보험금 청구금액}
claimDate: {type: string, description: 청구일자 YYYYMMDD, pattern: "^[0-9]{8}$"}
required: [claimNumber, claimAmount, claimDate]
additionalProperties: false
tags: [보험금, 청구처리]
legacy_interface_id: CLAIM0000001
required_env_keys: []
owner_org: MCP_TOOL

View File

@@ -0,0 +1,35 @@
name: oth_onnba3011_call
display_name: ONNBA3011 보험 업무 조회
version: 1.0.0
category_key: oth
description:
function: ONNBA3011 입력정보를 MCI 전문으로 변환해 보험 업무 결과를 조회한다.
when_to_use: 사용자가 ONNBA3011 인터페이스 기준의 보험 계약 또는 지급 정보를 조회하려는 경우 사용한다.
when_not_to_use: 인터페이스 입력값을 확인할 수 없거나 보험 정보를 변경하려는 경우에는 사용하지 않는다.
io_limits: CLCNNB00001 인터페이스 규격에 맞는 값만 전달하며 조회 결과를 업무 응답으로 변환한다.
display_description: ONNBA3011 업무 정보를 MCI로 조회합니다.
example_queries: ["고객의 보험 업무 정보를 조회해줘", "ONNBA3011 기준으로 계약 정보를 확인해줘", "입력한 고객번호의 보험 결과를 알려줘"]
read_only: true
destructive: false
idempotent: true
parameters_schema:
type: object
properties:
dalScCd: {type: string, description: 거래 구분 코드}
cstSucoRltyCd: {type: string, description: 고객 성공 관계 코드}
csNo: {type: string, description: 고객 번호}
rdreNo: {type: string, description: 설계사 번호}
unfcPvsCalReqYn: {type: string, description: 미확정 지급 계산 요청 여부}
kcisPymmTnnrRequest: {type: string, description: KCIS 납입 기간 요청값}
lmovYn: {type: string, description: 계약 이동 여부}
genPsthApvTrgtYn: {type: string, description: 일반 사후 승인 대상 여부}
ircoLmovEcpbTrgtYn: {type: string, description: 계약 이동 예외 대상 여부}
digCalYn: {type: string, description: 디지털 계산 여부}
prbuIciDigCalYn: {type: string, description: 상품별 디지털 계산 여부}
unfcPrbuIrcoAddu: {type: object, description: 미확정 상품 추가 정보, additionalProperties: true}
sucoIspaBasDto: {type: object, description: 성공 심사 기본 정보, additionalProperties: true}
additionalProperties: false
tags: [보험, MCI]
legacy_interface_id: CLCNNB00001
required_env_keys: [GLOW_COMMUNICATION_MCI_HOST, GLOW_COMMUNICATION_MCI_PORT]
owner_org: MCP_TOOL

View File

@@ -0,0 +1,22 @@
name: smp_exchange_inquiry
display_name: 실시간 환율 조회
version: 1.0.0
category_key: smp
description:
function: 통화코드를 기준으로 현재 환율 정보를 조회한다.
when_to_use: 사용자가 USD, EUR, JPY 등 특정 통화의 환율을 확인하려는 경우 사용한다.
when_not_to_use: 환전 거래를 실행하거나 과거 환율 통계를 분석하려는 경우에는 사용하지 않는다.
io_limits: 지원되는 통화코드의 조회 시점 환율만 반환하며 실제 환전 기능은 제공하지 않는다.
display_description: 지정한 통화의 현재 환율을 조회합니다.
example_queries: ["오늘 달러 환율을 알려줘", "엔화 환율이 얼마인지 조회해줘", "유로 환율을 확인해줘"]
read_only: true
destructive: false
idempotent: true
parameters_schema:
type: object
properties:
currencyCode: {type: string, description: 조회할 ISO 통화코드, pattern: "^[A-Z]{3}$"}
additionalProperties: false
tags: [환율, 금융]
required_env_keys: []
owner_org: MCP_TOOL

View File

@@ -0,0 +1,22 @@
name: smp_quote_daily
display_name: 오늘의 명언 조회
version: 1.0.0
category_key: smp
description:
function: 선택한 카테고리에 맞는 오늘의 명언 한 건을 조회한다.
when_to_use: 사용자가 명언이나 짧은 동기부여 문구를 요청한 경우 사용한다.
when_not_to_use: 업무 데이터 조회나 사실 검증이 필요한 질문에는 사용하지 않는다.
io_limits: 등록된 명언 데이터 중 한 건만 반환하며 출처 정보가 없을 수 있다.
display_description: 카테고리에 맞는 오늘의 명언을 제공합니다.
example_queries: ["오늘 힘이 되는 말을 알려줘", "업무 시작 전에 명언 하나 보여줘", "성공에 관한 짧은 문구를 추천해줘"]
read_only: true
destructive: false
idempotent: false
parameters_schema:
type: object
properties:
category: {type: string, description: 조회할 명언 카테고리}
additionalProperties: false
tags: [명언, 콘텐츠]
required_env_keys: []
owner_org: MCP_TOOL

View File

@@ -0,0 +1,22 @@
name: smp_team_list
display_name: MCP·TOOL 파트 구성원 조회
version: 1.0.0
category_key: smp
description:
function: 신한라이프 AX 추진팀과 MCP·TOOL 파트 구성원 및 직급을 조회한다.
when_to_use: 사용자가 프로젝트 담당자나 팀별 구성원 정보를 묻는 경우 사용한다.
when_not_to_use: 인사 개인정보나 연락처 또는 조직 변경 이력을 요청하는 경우에는 사용하지 않는다.
io_limits: 사전에 등록된 구성원의 이름과 직급만 반환하며 민감한 인사정보는 포함하지 않는다.
display_description: 신한라이프 MCP·TOOL 파트 담당자와 구성원을 조회합니다.
example_queries: ["MCP 팀 담당자를 알려줘", "TOOL 파트 구성원이 누구인지 보여줘", "AX 추진팀 담당자를 찾아줘"]
read_only: true
destructive: false
idempotent: true
parameters_schema:
type: object
properties:
teamName: {type: string, description: 조회할 팀 이름 또는 전체}
additionalProperties: false
tags: [조직, 담당자]
required_env_keys: []
owner_org: MCP_TOOL

View File

@@ -0,0 +1,23 @@
name: smp_weather_inquiry
display_name: 도시 날씨 조회
version: 1.0.0
category_key: smp
description:
function: 도시명을 기준으로 현재 날씨, 온도와 풍속을 조회한다.
when_to_use: 사용자가 특정 도시의 현재 기상 정보를 요청한 경우 사용한다.
when_not_to_use: 장기 예보나 기상 특보 또는 공식 재난정보가 필요한 경우에는 사용하지 않는다.
io_limits: 입력한 도시의 현재 관측 기반 샘플 정보만 반환한다.
display_description: 지정한 도시의 현재 날씨 정보를 조회합니다.
example_queries: ["서울 날씨를 알려줘", "부산의 현재 온도를 조회해줘", "제주도 바람이 얼마나 부는지 알려줘"]
read_only: true
destructive: false
idempotent: true
parameters_schema:
type: object
properties:
city: {type: string, description: 날씨를 조회할 도시명}
required: [city]
additionalProperties: false
tags: [날씨, 조회]
required_env_keys: []
owner_org: MCP_TOOL

View File

@@ -0,0 +1,24 @@
name: sol_request_detail
display_name: SOL 의뢰서 상세 조회
version: 1.0.0
category_key: sol
description:
function: SOL 의뢰서 식별자를 기준으로 의뢰서 상세 내용을 조회한다.
when_to_use: 사용자가 특정 SOL 의뢰서의 상세 내용과 진행 정보를 확인하려는 경우 사용한다.
when_not_to_use: 의뢰서 목록만 찾거나 의뢰서를 변경 또는 처리하려는 경우에는 사용하지 않는다.
io_limits: 정확한 의뢰서 ID가 필요하며 등록된 상세 정보만 반환한다.
display_description: SOL 의뢰서 한 건의 상세 정보를 조회합니다.
example_queries: ["이 SOL 의뢰서 상세를 보여줘", "의뢰서 ID로 처리 내용을 확인해줘", "선택한 의뢰서의 상세 정보를 알려줘"]
read_only: true
destructive: false
idempotent: true
parameters_schema:
type: object
properties:
srId: {type: string, description: 상세 조회할 SOL 의뢰서 ID}
required: [srId]
additionalProperties: false
tags: [SOL, 의뢰서]
legacy_interface_id: SOLG00000002
required_env_keys: []
owner_org: MCP_TOOL

View File

@@ -0,0 +1,25 @@
name: sol_request_list
display_name: SOL 의뢰서 목록 조회
version: 1.0.0
category_key: sol
description:
function: 진행상태, 조회기간과 조회대상 조건으로 SOL 의뢰서 목록을 조회한다.
when_to_use: 사용자가 자신의 SOL 의뢰서나 상태별 의뢰서 목록을 확인하려는 경우 사용한다.
when_not_to_use: 특정 의뢰서의 상세 내용만 보거나 의뢰서를 변경하려는 경우에는 사용하지 않는다.
io_limits: 입력 조건에 해당하는 의뢰서 요약 목록만 반환한다.
display_description: 조건에 맞는 SOL 의뢰서 목록을 조회합니다.
example_queries: ["진행 중인 SOL 의뢰서를 보여줘", "최근 한 달간 내 의뢰서를 조회해줘", "완료된 의뢰서 목록을 알려줘"]
read_only: true
destructive: false
idempotent: true
parameters_schema:
type: object
properties:
status: {type: string, description: 조회할 의뢰서 진행상태}
period: {type: string, description: 조회할 기간 조건}
target: {type: string, description: 나의 업무 또는 전체 조회대상}
additionalProperties: false
tags: [SOL, 의뢰서]
legacy_interface_id: SOLG00000001
required_env_keys: []
owner_org: MCP_TOOL