이호영 이호영 2025-03-18
알림톡 수신자 목록 타블레이터로 수정
@3afef8e37cd1476f2f6d4224c4d1b75a4f0dc834
src/main/java/itn/let/kakao/kakaoComm/KakaoReturnVO.java
--- src/main/java/itn/let/kakao/kakaoComm/KakaoReturnVO.java
+++ src/main/java/itn/let/kakao/kakaoComm/KakaoReturnVO.java
@@ -4,6 +4,9 @@
 import java.util.List;
 
 import itn.com.cmm.ComDefaultVO;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
 
 /**
 * @FileName : KakaoReturnVO.java
@@ -13,6 +16,9 @@
 
 * @프로그램 설명 : 카카오톡 리턴 변수 목록
 */
+@Getter
+@Setter
+@ToString
 public class KakaoReturnVO extends ComDefaultVO{
 	
 	private static final long serialVersionUID = 1L;
@@ -130,415 +136,4 @@
 	
 	private String businessType = "";					//카카오톡 채널 비즈니스 인증 타입
 	
-	public static long getSerialversionuid() {
-		return serialVersionUID;
-	}
-
-	public String getBizReturnMsg() {
-		return bizReturnMsg;
-	}
-
-	public void setBizReturnMsg(String bizReturnMsg) {
-		this.bizReturnMsg = bizReturnMsg;
-	}
-
-	public String getBizReturnCode() {
-		return bizReturnCode;
-	}
-
-	public void setBizReturnCode(String bizReturnCode) {
-		this.bizReturnCode = bizReturnCode;
-	}
-
-	public String getProfileId() {
-		return profileId;
-	}
-
-	public void setProfileId(String profileId) {
-		this.profileId = profileId;
-	}
-
-	public String getTotalCount() {
-		return totalCount;
-	}
-
-	public void setTotalCount(String totalCount) {
-		this.totalCount = totalCount;
-	}
-
-	public String getTotalPage() {
-		return totalPage;
-	}
-
-	public void setTotalPage(String totalPage) {
-		this.totalPage = totalPage;
-	}
-
-	public String getCurrentPage() {
-		return currentPage;
-	}
-
-	public void setCurrentPage(String currentPage) {
-		this.currentPage = currentPage;
-	}
-
-	public String getSenderKey() {
-		return senderKey;
-	}
-
-	public void setSenderKey(String senderKey) {
-		this.senderKey = senderKey;
-	}
-
-	public String getSenderKeyType() {
-		return senderKeyType;
-	}
-
-	public void setSenderKeyType(String senderKeyType) {
-		this.senderKeyType = senderKeyType;
-	}
-
-	public String getTemplateCode() {
-		return templateCode;
-	}
-
-	public void setTemplateCode(String templateCode) {
-		this.templateCode = templateCode;
-	}
-
-	public String getTemplateName() {
-		return templateName;
-	}
-
-	public void setTemplateName(String templateName) {
-		this.templateName = templateName;
-	}
-
-	public String getCategoryCode() {
-		return categoryCode;
-	}
-
-	public void setCategoryCode(String categoryCode) {
-		this.categoryCode = categoryCode;
-	}
-
-	public String getCreatedAt() {
-		return createdAt;
-	}
-
-	public void setCreatedAt(String createdAt) {
-		this.createdAt = createdAt;
-	}
-
-	public String getModifiedAt() {
-		return modifiedAt;
-	}
-
-	public void setModifiedAt(String modifiedAt) {
-		this.modifiedAt = modifiedAt;
-	}
-
-	public String getServiceStatus() {
-		return serviceStatus;
-	}
-
-	public void setServiceStatus(String serviceStatus) {
-		this.serviceStatus = serviceStatus;
-	}
-
-	public List<KakaoReturnVO> getTemplatList() {
-		return templatList;
-	}
-
-	public void setTemplatList(List<KakaoReturnVO> templatList) {
-		this.templatList = templatList;
-	}
-
-	public String getUuid() {
-		return uuid;
-	}
-
-	public void setUuid(String uuid) {
-		this.uuid = uuid;
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	public String getStatus() {
-		return status;
-	}
-
-	public void setStatus(String status) {
-		this.status = status;
-	}
-
-	public boolean isBlock() {
-		return block;
-	}
-
-	public void setBlock(boolean block) {
-		this.block = block;
-	}
-
-	public boolean isDormant() {
-		return dormant;
-	}
-
-	public void setDormant(boolean dormant) {
-		this.dormant = dormant;
-	}
-
-	public String getTitle() {
-		return title;
-	}
-
-	public void setTitle(String title) {
-		this.title = title;
-	}
-
-	public String getDescription() {
-		return description;
-	}
-
-	public void setDescription(String description) {
-		this.description = description;
-	}
-
-	public String getImageUrl() {
-		return imageUrl;
-	}
-
-	public void setImageUrl(String imageUrl) {
-		this.imageUrl = imageUrl;
-	}
-
-	public String getListTitle() {
-		return listTitle;
-	}
-
-	public void setListTitle(String listTitle) {
-		this.listTitle = listTitle;
-	}
-
-	public String getListDescription() {
-		return listDescription;
-	}
-
-	public void setListDescription(String listDescription) {
-		this.listDescription = listDescription;
-	}
-
-	public String getSumTitle() {
-		return sumTitle;
-	}
-
-	public void setSumTitle(String sumTitle) {
-		this.sumTitle = sumTitle;
-	}
-
-	public String getSumDescription() {
-		return sumDescription;
-	}
-
-	public void setSumDescription(String sumDescription) {
-		this.sumDescription = sumDescription;
-	}
-
-	public String getProfileStatus() {
-		return profileStatus;
-	}
-
-	public void setProfileStatus(String profileStatus) {
-		this.profileStatus = profileStatus;
-	}
-
-	public boolean isAlimtalk() {
-		return alimtalk;
-	}
-
-	public void setAlimtalk(boolean alimtalk) {
-		this.alimtalk = alimtalk;
-	}
-
-	public boolean isBizchat() {
-		return bizchat;
-	}
-
-	public void setBizchat(boolean bizchat) {
-		this.bizchat = bizchat;
-	}
-
-	public boolean isBrandtalk() {
-		return brandtalk;
-	}
-
-	public void setBrandtalk(boolean brandtalk) {
-		this.brandtalk = brandtalk;
-	}
-
-	public String getCommittalCompanyName() {
-		return committalCompanyName;
-	}
-
-	public void setCommittalCompanyName(String committalCompanyName) {
-		this.committalCompanyName = committalCompanyName;
-	}
-
-	public String getChannelKey() {
-		return channelKey;
-	}
-
-	public void setChannelKey(String channelKey) {
-		this.channelKey = channelKey;
-	}
-
-	public boolean isBusinessProfile() {
-		return businessProfile;
-	}
-
-	public void setBusinessProfile(boolean businessProfile) {
-		this.businessProfile = businessProfile;
-	}
-
-	public String getBusinessType() {
-		return businessType;
-	}
-
-	public void setBusinessType(String businessType) {
-		this.businessType = businessType;
-	}
-
-	public String getTemplateMessageType() {
-		return templateMessageType;
-	}
-
-	public void setTemplateMessageType(String templateMessageType) {
-		this.templateMessageType = templateMessageType;
-	}
-
-	public String getTemplateEmphasizeType() {
-		return templateEmphasizeType;
-	}
-
-	public void setTemplateEmphasizeType(String templateEmphasizeType) {
-		this.templateEmphasizeType = templateEmphasizeType;
-	}
-
-	public String getTemplateContent() {
-		return templateContent;
-	}
-
-	public void setTemplateContent(String templateContent) {
-		this.templateContent = templateContent;
-	}
-
-	public String getTemplateExtra() {
-		return templateExtra;
-	}
-
-	public void setTemplateExtra(String templateExtra) {
-		this.templateExtra = templateExtra;
-	}
-	
-	public String getTemplateAd() {
-		return templateAd;
-	}
-
-	public void setTemplateAd(String templateAd) {
-		this.templateAd = templateAd;
-	}
-
-	public String getTemplateImageName() {
-		return templateImageName;
-	}
-
-	public void setTemplateImageName(String templateImageName) {
-		this.templateImageName = templateImageName;
-	}
-
-	public String getTemplateImageUrl() {
-		return templateImageUrl;
-	}
-
-	public void setTemplateImageUrl(String templateImageUrl) {
-		this.templateImageUrl = templateImageUrl;
-	}
-
-	public String getTemplateTitle() {
-		return templateTitle;
-	}
-
-	public void setTemplateTitle(String templateTitle) {
-		this.templateTitle = templateTitle;
-	}
-
-	public String getTemplateSubtitle() {
-		return templateSubtitle;
-	}
-
-	public void setTemplateSubtitle(String templateSubtitle) {
-		this.templateSubtitle = templateSubtitle;
-	}
-
-	public String getTemplateHeader() {
-		return templateHeader;
-	}
-
-	public void setTemplateHeader(String templateHeader) {
-		this.templateHeader = templateHeader;
-	}
-
-	public Boolean getSecurityFlag() {
-		return securityFlag;
-	}
-
-	public void setSecurityFlag(Boolean securityFlag) {
-		this.securityFlag = securityFlag;
-	}
-
-	public String getInspectionStatus() {
-		return inspectionStatus;
-	}
-
-	public void setInspectionStatus(String inspectionStatus) {
-		this.inspectionStatus = inspectionStatus;
-	}
-
-	public List<KakaoButtonVO> getButtonList() {
-		return buttonList;
-	}
-
-	public void setButtonList(List<KakaoButtonVO> buttonList) {
-		this.buttonList = buttonList;
-	}
-
-	public List<KakaoButtonVO> getQuickReplyList() {
-		return quickReplyList;
-	}
-
-	public void setQuickReplyList(List<KakaoButtonVO> quickReplyList) {
-		this.quickReplyList = quickReplyList;
-	}
-
-	public List<KakaoCommentVO> getCommentList() {
-		return commentList;
-	}
-
-	public void setCommentList(List<KakaoCommentVO> commentList) {
-		this.commentList = commentList;
-	}
-
-	public List<KakaoItemVO> getItemList() {
-		return itemList;
-	}
-
-	public void setItemList(List<KakaoItemVO> itemList) {
-		this.itemList = itemList;
-	}
 }
src/main/java/itn/let/kakao/kakaoComm/KakaoSendAdvcVO.java
--- src/main/java/itn/let/kakao/kakaoComm/KakaoSendAdvcVO.java
+++ src/main/java/itn/let/kakao/kakaoComm/KakaoSendAdvcVO.java
@@ -41,8 +41,9 @@
 	private String subMsgSendYn;		// 대체문자 전송 여부
 	private String subMsgTxt;			// 대체문자 내용
 	private String subMsgType;			// 대체문자 타입
-	private String bizJsonName;			// JSON 파일명
 	private String reqDate;				// 예약일시
+	
+	private String jsonStr;				// jsonStr
 
 	// =====
 	// =====
@@ -71,8 +72,8 @@
 			"\n , subMsgSendYn=[" + subMsgSendYn + "]" +
 			"\n , subMsgTxt=[" + subMsgTxt + "]" +
 			"\n , subMsgType=[" + subMsgType + "]" +
-			"\n , bizJsonName=[" + bizJsonName + "]" +
 			"\n , reqDate=[" + reqDate + "]" +
+			"\n , jsonStr=[" + jsonStr + "]" +
 			"\n , ==== MJ_MSG_DATA INSERT DATA END =======" +
 			"\n " +
 			"\n , smsPrice=[" + smsPrice + "]" +
src/main/java/itn/let/kakao/kakaoComm/KakaoSendUtil.java
--- src/main/java/itn/let/kakao/kakaoComm/KakaoSendUtil.java
+++ src/main/java/itn/let/kakao/kakaoComm/KakaoSendUtil.java
@@ -2,12 +2,12 @@
 
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.annotation.Resource;
 
@@ -18,6 +18,7 @@
 
 import itn.com.cmm.util.StringUtil;
 import itn.let.kakao.kakaoComm.kakaoApi.KakaoApiJsonSave;
+import itn.let.kakao.kakaoComm.kakaoApi.KakaoApiTemplate;
 import itn.let.mail.service.StatusResponse;
 import itn.let.mjo.mjocommon.MjonCommon;
 import itn.let.mjo.msg.service.MjonMsgVO;
@@ -39,6 +40,9 @@
 	private MjonMsgDataService mjonMsgDataService;
 
 	@Autowired
+	KakaoApiTemplate kakaoApiTemplate;
+
+	@Autowired
 	private PriceAndPoint priceAndPoint;
 
 	@Autowired
@@ -52,7 +56,7 @@
 	public static final String LONG_MSG_TYPE = "MMS";
 	
 	/**
-	 * @methodName	: kakaoSendPrice_advc 
+	 * @methodName	: populateSendLists _advc
 	 * @author		: 이호영
 	 * @date		: 2025. 3. 7.
 	 * @description	: 기존 kakaoSendPrice 개선
@@ -91,20 +95,40 @@
 
 		Calendar calendar = Calendar.getInstance();
 		calendar.setTime(baseDate);  // calendar에 baseDate 설정
+
+		
+		KakaoReturnVO templateDetail = kakaoApiTemplate.selectKakaoApiTemplateDetail(kakaoVO);
+		log.info(" + templateDetail :: [{}]", templateDetail.toString());
+		String templateContent = templateDetail.getTemplateContent(); // 알림톡 템플릿
+		String templateTitle = templateDetail.getTemplateTitle();
+		log.info(" + templateTitle :: [{}]",templateTitle);
+		
+		kakaoVO.setTxtReplYn(replYnChecker(templateContent+" "+templateTitle));
+
+		String subMsgTxt = kakaoVO.getSubMsgTxt(); // 실패 대체 문자
+		
+		// 시스템 기본 단가 정보 불러오기
+		JoinSettingVO sysJoinSetVO = mjonMsgDataService.selectJoinSettingInfo();
+		// 사용자 개인 단가 정보 불러오기
+		MberManageVO mberManageVO = mjonMsgDataService.selectMberManageInfo(kakaoVO.getUserId());
+		
 		
 		int counter = 0;  // 분할 건수 카운터
-		
 		// Map에 총 갯수가 수신자 갯수와 동일함
 		for(Map<String, String> variables : kakaoVO.getVarListMap()) {
 			
 			KakaoSendAdvcVO sendVO = new KakaoSendAdvcVO();
 			
-			
+// 공통 기본값
+			sendVO.setMsgType("8");
+			sendVO.setSenderKey(kakaoVO.getSenderKey());
+			sendVO.setTemplateCode(kakaoVO.getTemplateCode());
+			sendVO.setUserId(kakaoVO.getUserId()); // 수신자
+			sendVO.setCallFrom(kakaoVO.getCallFrom()); // 발신자
+			sendVO.setAgentCode("04"); // 발신자
 
 			// step1
 			// Step 1-1: 값 치환 및 수신번호 셋팅
-			String templateContent = kakaoVO.getTemplateContent(); // 알림톡 템플릿
-			
 			// Step 1-2: 수신자 정보 설정 (callToList는 항상 설정).
 			if (variables.containsKey("callToList")) {
 				sendVO.setCallTo(variables.get("callToList"));
@@ -114,17 +138,19 @@
 			// Step 1-3: 템플릿 치환데이터 설정
 			// TxtReplYn이 "Y"일 때만 치환 수행
 			if ("Y".equals(kakaoVO.getTxtReplYn())) {
-				templateContent = replaceTemplateVariables(templateContent, variables);
+				templateContent = mjonCommon.ATReplaceTemplateVariables(templateContent, variables);
+				if("TEXT".equals(templateDetail.getTemplateEmphasizeType())) {
+					templateTitle = mjonCommon.ATReplaceTemplateVariables(templateTitle, variables);
+				}
 			}
+			sendVO.setTemplateTitle(templateTitle);
 			sendVO.setTemplateContent(templateContent);
-			
+
 
 			// Step 1-4: 실패 대체 문자 치환데이터 설정
-			if("Y".equals(kakaoVO.getSubMsgSendYn())) {
-				String subMsgTxt = kakaoVO.getSubMsgTxt(); // 실패 대체 치환 문자
-				// TxtReplYn이 "Y"일 때만 치환 수행
-				if ("Y".equals(kakaoVO.getSubMsgTxtReplYn())) {
-					subMsgTxt = replaceTemplateVariables(subMsgTxt, variables);
+			if("Y".equals(kakaoVO.getSubMsgSendYn())) { // 대체문자가 있나?
+				if ("Y".equals(kakaoVO.getSubMsgTxtReplYn())) { // 치환데이터가 있나?
+					subMsgTxt = mjonCommon.ATReplaceTemplateVariables(subMsgTxt, variables);
 				}
 				sendVO.setSubMsgTxt(subMsgTxt);// 실패 
 			}
@@ -136,43 +162,41 @@
 								);
 			*/
 
+			// Step1 END
 
 			
-			// step3
-			// 바이트 수 체크 및 금액설정
+// step3
+// 바이트 수 체크 및 금액설정
 			
-			// 시스템 기본 단가 정보 불러오기
-			JoinSettingVO sysJoinSetVO = mjonMsgDataService.selectJoinSettingInfo();
 			
-			// 사용자 개인 단가 정보 불러오기
-			MberManageVO mberManageVO = mjonMsgDataService.selectMberManageInfo(kakaoVO.getUserId());
 			Float kakaoAtPrice = mberManageVO.getKakaoAtPrice();
 			// 유효한 단가 계산
 			float shortPrice = getValidPrice(mberManageVO.getShortPrice(), sysJoinSetVO.getShortPrice());
 			float longPrice = getValidPrice(mberManageVO.getLongPrice(), sysJoinSetVO.getLongPrice());
 			
 			// 공통 가격 설정
-			kakaoVO.setSmsPrice(shortPrice);
-			kakaoVO.setMmsPrice(longPrice);
+			sendVO.setSmsPrice(shortPrice);
+			sendVO.setMmsPrice(longPrice);
 			
 			
 			if("Y".equals(kakaoVO.getSubMsgSendYn())) {
 				int smsTxtByte = mjonCommon.getSmsTxtBytes(sendVO.getSubMsgTxt());
-				
 				String sendType = getMsgType(smsTxtByte);
+				sendVO.setSubMsgType(sendType);
 
 				if ("INVALID".equals(sendType)) {
 					statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "전송 문자 길이를 초과하였습니다.");return kakaoSendAdvcListVO;
 				}
 	
 				boolean isMms = "MMS".equals(sendType);
-				kakaoAtPrice = isMms ? longPrice : shortPrice;
-				kakaoVO.setKakaoAtPrice(getValidPrice(mberManageVO.getKakaoAtPrice(), sysJoinSetVO.getKakaoAtPrice()));
+//				kakaoAtPrice = isMms ? longPrice : shortPrice;
+//				sendVO.setKakaoAtPrice(getValidPrice(mberManageVO.getKakaoAtPrice(), sysJoinSetVO.getKakaoAtPrice()));
+				sendVO.setKakaoAtPrice(isMms ? longPrice : shortPrice);
 				
 				
 			} else {
 				kakaoAtPrice = getValidPrice(mberManageVO.getKakaoAtPrice(), sysJoinSetVO.getKakaoAtPrice());
-				kakaoVO.setKakaoAtPrice(kakaoAtPrice);
+				sendVO.setKakaoAtPrice(kakaoAtPrice);
 			}
 			
 			
@@ -181,7 +205,7 @@
 			// 예약 시간 설정 및 분할 데이터 설정
 			if ("Y".equalsIgnoreCase(kakaoVO.getReserveYn())) {
 				// 분할 발송일 경우
-				if ("on".equalsIgnoreCase(kakaoVO.getDivideChk())) {
+				if ("Y".equalsIgnoreCase(kakaoVO.getDivideChk())) {
 					if (counter == Integer.parseInt(kakaoVO.getDivideCnt()))  { // 지정된 건수마다 간격 추가
 						counter = 0;
 						calendar.add(Calendar.MINUTE, Integer.parseInt(kakaoVO.getDivideTime()));
@@ -190,26 +214,20 @@
 				}
 				// 예약 시간 설정
 			}
-			
 			// 즉시 발송인경우 현재 시간 
 			// 예약인 경우 위에 설정한 시간 입력
 			sendVO.setReqDate(sdf.format(calendar.getTime())); 
 			
 			
-			
-			
-			
-			
 			// step5
-			// 전송 메세지 설정 kakaoSendMsg 참고
+			// 전송 메세지 설정 json파일 만들기
+			if("Y".equals(kakaoVO.getBizJsonYn())) {
+				String jsonStr = kakaoApiJsonSave.kakaoApiJsonSave_advc(sendVO, templateDetail);
+				sendVO.setJsonStr(jsonStr);
+			}
+//			log.info(" + sendVO :: [{}]", sendVO.toString());
 			
-			
-
-//			kakaoSendAdvcVO.setSendType("AT");
-			sendVO.setMsgType("8");
-			sendVO.setUserId(kakaoVO.getUserId());
-			
-			
+			kakaoSendAdvcListVO.add(sendVO);
 		}
 		
 		
@@ -217,6 +235,17 @@
 	}
 	
 	
+	private String replYnChecker(String input) {
+		// #{...} 패턴을 확인하는 정규 표현식
+		String regex = "#\\{[^}]+\\}";
+		Pattern pattern = Pattern.compile(regex);
+		Matcher matcher = pattern.matcher(input);
+		
+		// 패턴이 존재하면 "Y", 없으면 "N" 반환
+		return matcher.find() ? "Y" : "N";
+	}
+
+
 	public Float getValidPrice(Float personalPrice, Float defaultPrice) {
 		return (personalPrice != null && personalPrice > 0) ? personalPrice : defaultPrice;
 	}
@@ -251,25 +280,7 @@
 	}
 
 
-	/**
-	 * @methodName	: replaceTemplateVariables 
-	 * @author		: 이호영
-	 * @date		: 2025. 3. 12.
-	 * @description	: 헬퍼 메서드: 템플릿 변수 치환
-	 * @return : String
-	 * @param content
-	 * @param variables
-	 * @return
-	 */
-	private String replaceTemplateVariables(String content, Map<String, String> variables) {
-		String result = content;
-		for (Map.Entry<String, String> entry : variables.entrySet()) {
-			String placeholder = entry.getKey();
-			String value = entry.getValue();
-			result = result.replace(placeholder, value);
-		}
-		return result;
-	}
+
 	
 	
 	
@@ -312,8 +323,6 @@
 				String tempSubMagTxt = kakaoVO.getSubMsgTxt().replace("\r\n", "\n");
 				if(kakaoVO.getSubMsgTxtReplYn().equals("Y")) {
 					tempSubMagTxt = kakaoSubMagTxtRepl(tempSubMagTxt, kakaoVO, count);
-//					tempSubMagTxt = kakaoSubMagTxtRepl_advc(tempSubMagTxt, kakaoVO, count);
-					System.out.println("+ tempSubMagTxt :: "+ tempSubMagTxt);
 				}
 				int bytes = tempSubMagTxt.getBytes(charset).length;
 				
@@ -649,6 +658,106 @@
 						varValInfo = kakaoVO.getVarValList().get(count);
 					}
 					String jsonFileName = kakaoApiJsonSave.kakaoApiJsonSave(kakaoVO, varValInfo);
+					setSendMsgVO.setBizJsonName(jsonFileName); //json 파일명
+				}
+				
+				kakaoSendList.add(setSendMsgVO);
+			}
+			kakaoVO.setKakaoSendList(kakaoSendList);
+			
+		} catch (Exception e) {
+			System.out.println(e.toString());
+			e.printStackTrace();
+		}
+		
+		return kakaoVO;
+	}
+	
+	/**
+	 * @methodName	: kakaoSendMsg_advc 
+	 * @author		: 이호영
+	 * @date		: 2025. 3. 13.
+	 * @description	: kakaoSendMsg 개선
+	 * @return : KakaoVO
+	 * @param kakaoVO
+	 * @return
+	 * @throws Exception
+	 * 
+	 */
+	public KakaoVO kakaoSendMsg_advc(KakaoVO kakaoVO) throws Exception {
+		List<KakaoVO> kakaoSendList = new ArrayList<KakaoVO>();
+		//전체 받는사람 수량만큼 반복 확인
+		int callToCnt = kakaoVO.getCallToList().length;	
+		try {
+			for(int count =0; count < callToCnt; count++) {
+				
+				KakaoVO setSendMsgVO = new KakaoVO();
+				
+				setSendMsgVO.setDestPhone(kakaoVO.getCallToList()[count]); // 수신 번호
+				// 카카오 전송내용 설정
+				// 변환문자 포함(Y), 미포함(N)
+				if(kakaoVO.getTxtReplYn().equals("Y")) {
+					
+					String templateContent = kakaoSubMagTxtRepl(kakaoVO.getTemplateContent(), kakaoVO, count);
+					setSendMsgVO.setTemplateContent(templateContent);
+					
+					if(kakaoVO.getTemplateEmphasizeType().equals("TEXT")) {
+						
+						String title = kakaoSubMagTxtRepl(kakaoVO.getTemplateTitle(), kakaoVO, count);
+						String subTitle = kakaoVO.getTemplateSubtitle();
+//						title = title +"§§"+ subTitle;
+						setSendMsgVO.setTemplateEmphasizeType(kakaoVO.getTemplateEmphasizeType());
+						setSendMsgVO.setTemplateTitle(title);
+					}
+					
+				}else {
+					
+					if(kakaoVO.getTemplateEmphasizeType().equals("TEXT")) {
+						
+						String title = kakaoSubMagTxtRepl(kakaoVO.getTemplateTitle(), kakaoVO, count);
+						String subTitle = kakaoVO.getTemplateSubtitle();
+//						title = title +"§§"+ subTitle;
+						setSendMsgVO.setTemplateEmphasizeType(kakaoVO.getTemplateEmphasizeType());
+						setSendMsgVO.setTemplateTitle(title);
+					}
+					
+					// 템플릿 내용 설정
+					setSendMsgVO.setTemplateContent(kakaoVO.getTemplateContent());
+				}
+				
+				//대체문자 포함(Y), 미포함(N)
+				if(kakaoVO.getSubMsgSendYn().equals("Y")) {
+					
+					String charset = "euc-kr";	//문자 바이트 계산에 필요한 캐릭터 셋 : 한글 2Byte로 계산
+					
+					String tempSubMagTxt = kakaoVO.getSubMsgTxt().replace("\r\n", "\n");
+					kakaoVO.setKakaoSubMagOrgnlTxt(tempSubMagTxt);
+					if(kakaoVO.getSubMsgTxtReplYn().equals("Y")) {
+						tempSubMagTxt = kakaoSubMagTxtRepl(tempSubMagTxt, kakaoVO, count);
+					}
+					System.out.println("@@  대체문자내용 : " + tempSubMagTxt);
+					setSendMsgVO.setSubMsgTxt(tempSubMagTxt);
+					
+					int FrBytes = tempSubMagTxt.getBytes(charset).length;
+					System.out.println("@@  대체문자길이 : " + FrBytes);
+					//메세지 길이가 90Byte가 초과시 MMS
+					if(FrBytes > 90) {
+						setSendMsgVO.setSubMsgType("MMS");
+					}else {// 아니면 SMS
+						setSendMsgVO.setSubMsgType("SMS");
+					}
+					
+					System.out.println("@@  대체문자타입 : " + setSendMsgVO.getSubMsgType());
+				}
+				
+				if(kakaoVO.getBizJsonYn().equals("Y")) {
+					kakaoVO.setDestPhone(kakaoVO.getCallToList()[count]); // 수신 번호
+					
+					String[] varValInfo = null;
+					if( kakaoVO.getVarValList().size() != 0) {
+						varValInfo = kakaoVO.getVarValList().get(count);
+					}
+					String jsonFileName = kakaoApiJsonSave.kakaoApiJsonSave(kakaoVO, varValInfo);
 //					String jsonFileName = kakaoApiJsonSave.kakaoApiJsonSave_advc(kakaoVO, varValInfo);
 //					String jsonFileName = kakaoApiJsonSave.kakaoApiJsonSave(kakaoVO, kakaoVO.getVarValList().get(count));
 					setSendMsgVO.setBizJsonName(jsonFileName); //json 파일명
src/main/java/itn/let/kakao/kakaoComm/kakaoApi/KakaoApiJsonSave.java
--- src/main/java/itn/let/kakao/kakaoComm/kakaoApi/KakaoApiJsonSave.java
+++ src/main/java/itn/let/kakao/kakaoComm/kakaoApi/KakaoApiJsonSave.java
@@ -8,6 +8,7 @@
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
@@ -18,6 +19,7 @@
 import itn.com.cmm.util.StringUtil;
 import itn.let.kakao.kakaoComm.KakaoButtonVO;
 import itn.let.kakao.kakaoComm.KakaoReturnVO;
+import itn.let.kakao.kakaoComm.KakaoSendAdvcVO;
 import itn.let.kakao.kakaoComm.KakaoVO;
 
 @Component
@@ -32,33 +34,7 @@
 	
 	static String json;
 	
-	public String kakaoApiJsonSave_advc(KakaoVO kakaoVO, String[] varValInfo) {
-		// json파일 저장
-		
-		
-		Date nowDate = new Date();
-		SimpleDateFormat todayFrom = new SimpleDateFormat("yyyyMMdd");
-		SimpleDateFormat timeFrom = new SimpleDateFormat("HHmmss");
-		String jsonFileName = mjonBizJsonDir+"/"+kakaoVO.getUserId()+"/"+todayFrom.format(nowDate)+"/"+kakaoVO.getSendType(); // 아이디/날짜/타입
-		
-		String fileName = timeFrom.format(nowDate)+"_"+kakaoVO.getDestPhone()+".json";
-		
-		
-//		File userIdFile = new File(jsonFileName);
-//		if(!userIdFile.exists()) {
-//			userIdFile.mkdirs(); // 없으면 하위 디렉토리 까지 생성
-//			jsonFileName = jsonFileName +"/"+fileName;
-//		}else {
-//			
-//			jsonFileName = jsonFileName +"/"+fileName;
-//			System.out.println("jsonFileName  : "+jsonFileName);
-//			File file1 = new File(jsonFileName);
-//			if (file1.isFile()) {
-//				return jsonFileName;
-//			}
-//		}
-		
-		KakaoReturnVO templateDetail = kakaoApiTemplate.selectKakaoApiTemplateDetail(kakaoVO);
+	public String kakaoApiJsonSave_advc(KakaoSendAdvcVO sendVO, KakaoReturnVO templateDetail) {
 		
 		// 버튼리스트 JSON 생성
 		JSONArray buttonList = new JSONArray();
@@ -86,44 +62,11 @@
 		JSONObject templateDetailInfo = new JSONObject();
 		String emphasizeType = templateDetail.getTemplateEmphasizeType();
 		
+		
 		if(emphasizeType.equals("TEXT")) {
-			
-			String templateTitle =  templateDetail.getTemplateTitle();
-			if(kakaoVO.getVarNmList().size() != 0) {
-				String[] varNm = new String[kakaoVO.getVarNmList().size()];
-				int q=0;
-				for(String temp : kakaoVO.getVarNmList()) {
-					temp = temp.replaceAll("\\#\\{" , "§§");
-					temp = temp.replaceAll("\\}" , "§");
-					varNm[q] = temp;
-					q++;
-				}
-				List<String[]> varValList = kakaoVO.getVarValList(); // value 값
-				
-				templateTitle = templateTitle.replaceAll(String.valueOf((char)13), "");
-				templateTitle = templateTitle.replaceAll("\\#\\{" , "§§");
-				templateTitle = templateTitle.replaceAll("\\}" , "§");
-				
-				for(int i=0; i < varNm.length; i++) {
-					for(int j=0; j < varValInfo.length; j++) {
-						if (templateTitle.indexOf(varNm[i]) > -1) {
-			 				if(varValInfo[j] != null) {
-			 					templateTitle = templateTitle.replaceAll(varNm[i] , StringUtil.getString(varValInfo[j]));
-			 				}else {
-			 					templateTitle = templateTitle.replaceAll(varNm[i] , "");
-			 				}
-			 			}
-					}
-				}
-				
-				templateDetailInfo.put("title", templateTitle);
-			}else {
-				templateDetailInfo.put("title", templateTitle);
-			}
+			templateDetailInfo.put("title", sendVO.getTemplateTitle());
 		}else if(emphasizeType.equals("IMAGE")) {
 			templateDetailInfo.put("msg_type", "ai");
-		}else if(emphasizeType.equals("NONE")) {
-			
 		}
 		
 		
@@ -138,16 +81,8 @@
 		
 		// 입력 json 데이터를 파일로 변경
 		String jsonStr = jo.toString();
-//		System.out.println("jsonStr  : "+jsonStr);
-	
-//			File outPut = new File(jsonFileName);
-//			outPut.createNewFile();
 		
-//			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outPut), "utf-8"));
-//			bw.write(jsonStr);
-//			bw.close();
-		
-		return jsonFileName;
+		return jsonStr;
 	}
 	
 	public String kakaoApiJsonSave(KakaoVO kakaoVO, String[] varValInfo) {
src/main/java/itn/let/kakao/user/kakaoAt/service/impl/KakaoAlimTalkServiceImpl.java
--- src/main/java/itn/let/kakao/user/kakaoAt/service/impl/KakaoAlimTalkServiceImpl.java
+++ src/main/java/itn/let/kakao/user/kakaoAt/service/impl/KakaoAlimTalkServiceImpl.java
@@ -1,6 +1,10 @@
 package itn.let.kakao.user.kakaoAt.service.impl;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
@@ -79,6 +83,9 @@
 
 	@Autowired
 	private MjonCommon mjonCommon;
+	
+	@Autowired
+	private PriceAndPoint priceAndPoint;
 	
 	//발신프로필 상태값 변경(삭제/복구 기능)
 	@Override
@@ -878,22 +885,47 @@
 		StatusResponse statusResponse = new StatusResponse();
 
 		
-/** 카카오톡 전송 기본 설정 -------------------------------------------*/
-				
-			
-/** 전송금액 설정 --------------------------------------------------*/
+/** 카카오톡 전송 설정 -------------------------------------------*/
 		List<KakaoSendAdvcVO> kakaoSendAdvcListVO = kakaoSendUtil.populateSendLists(kakaoVO, statusResponse);
 		if (statusResponse.getStatus() != null && !statusResponse.getStatus().equals(HttpStatus.OK)) {
 			log.error(" + populateSendLists 처리 중 오류 발생: {}", statusResponse.getMessage());
 			return statusResponse;
 		}
+
+		// 측정할 메소드 호출 전 시간 기록
+		Instant start = Instant.now();
+/** 전송금액 확인 --------------------------------------------------*/
+		if (!isCashSufficient(userId, kakaoSendAdvcListVO)) {
+			log.error("Insufficient balance for message sending.");
+			return new StatusResponse(HttpStatus.BAD_REQUEST, "문자 발송에 필요한 보유 잔액이 부족 합니다.");
+		}
+
+		// 측정할 메소드 호출 후 시간 기록
+		Instant end = Instant.now();
+		
+		log.info(" + start :: [{}]", start);
+		// 실행 시간 계산 (나노초, 밀리초, 초)
+		System.out.println("메소드 실행 시간 (초): " + Duration.between(start, end).getSeconds() + " s");
+		System.out.println("메소드 실행 시간 (Nano): " + Duration.between(start, end).getNano() + " Nano");
 		
 		
 		
 		
 		
-		// step 1
-//		List<String> idList = mjonCommon.getNextCustomMsgCId(mjonMsgSendVOList.size());
+		start = Instant.now();
+		List<String> idList = mjonCommon.getNextCustomMsgCId(kakaoSendAdvcListVO.size());
+		for (int i = 0; i < kakaoSendAdvcListVO.size(); i++) {
+			kakaoSendAdvcListVO.get(i).setMsgId(idList.get(i));
+		}
+		
+		// 측정할 메소드 호출 후 시간 기록
+		end = Instant.now();
+
+		log.info(" + start :: [{}]", start);
+		// 실행 시간 계산 (나노초, 밀리초, 초)
+		System.out.println("메소드 실행 시간 (초): " + Duration.between(start, end).getSeconds() + " s");
+		System.out.println("메소드 실행 시간 (Nano): " + Duration.between(start, end).getNano() + " Nano");
+		kakaoSendAdvcListVO.forEach(t-> log.info(" + t.toString() :: [{}]", t.toString()) );
 		
 		
 		
@@ -910,14 +942,6 @@
 		
 		
 		
-		
-		
-		
-		
-/** 카카오톡 전송 기본 설정 -------------------------------------------*/
-		kakaoVO.setSendType("AT");
-		kakaoVO.setMsgType("8");
-		kakaoVO.setUserId(userId);
 		
 		
 		
@@ -931,7 +955,27 @@
 	}
 	
 	
-	
+
+	// 보유 금액이 충분한지 확인하는 메서드
+	private boolean isCashSufficient(String userId, List<KakaoSendAdvcVO> kakaoSendAdvcListVO) throws Exception {
+		
+		
+		String userMoney = priceAndPoint.getBefCash(userId);
+		// 쉼표 제거
+		userMoney = userMoney.replace(",", "");
+
+		// 사용자 보유 금액 BigDecimal 변환 (HALF_EVEN 적용)
+		BigDecimal befCash = new BigDecimal(userMoney).setScale(2, RoundingMode.HALF_EVEN);
+
+		// 총 메시지 금액 계산 (HALF_EVEN 적용)
+		BigDecimal totalEachPrice = kakaoSendAdvcListVO.stream()
+			.map(msg -> new BigDecimal(String.valueOf(msg.getKakaoAtPrice()))) // 변환 오류 방지
+			.reduce(BigDecimal.ZERO, BigDecimal::add)
+			.setScale(2, RoundingMode.HALF_EVEN); // 일관성 유지
+
+		// 비교 수행
+		return befCash.compareTo(totalEachPrice) >= 0;
+	}
 	
 	
 	
src/main/java/itn/let/kakao/user/kakaoAt/web/KakaoAlimTalkSendController.java
--- src/main/java/itn/let/kakao/user/kakaoAt/web/KakaoAlimTalkSendController.java
+++ src/main/java/itn/let/kakao/user/kakaoAt/web/KakaoAlimTalkSendController.java
@@ -220,8 +220,8 @@
 			model.addAttribute("sendPrice", kakaoSendUtil.selectSendPriceOfKakaoAtAndSmsAndMms(userId));
 		}
 		
-//		return "web/kakao/msgdata/at/KakaoAlimtalkMsgDataView";
-		return "web/kakao/msgdata/at/KakaoAlimtalkMsgDataView_advcbackup_20250310";
+		return "web/kakao/msgdata/at/KakaoAlimtalkMsgDataView";
+//		return "web/kakao/msgdata/at/KakaoAlimtalkMsgDataView_advcbackup_20250310";
 									
 	}
 	
src/main/java/itn/let/mjo/mjocommon/MjonCommon.java
--- src/main/java/itn/let/mjo/mjocommon/MjonCommon.java
+++ src/main/java/itn/let/mjo/mjocommon/MjonCommon.java
@@ -6,6 +6,7 @@
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.Resource;
 
@@ -733,6 +734,27 @@
 //		log.info(" + smsBytes :: [{}]", smsBytes);
 		return smsBytes;
 	}
+
+
+	/**
+	 * @methodName	: replaceTemplateVariables 
+	 * @author		: 이호영
+	 * @date		: 2025. 3. 12.
+	 * @description	: 헬퍼 메서드: 템플릿 변수 치환
+	 * @return : String
+	 * @param content
+	 * @param variables
+	 * @return
+	 */
+	public static String ATReplaceTemplateVariables(String content, Map<String, String> variables) {
+		String result = content;
+		for (Map.Entry<String, String> entry : variables.entrySet()) {
+			String placeholder = entry.getKey();
+			String value = entry.getValue();
+			result = result.replace(placeholder, value);
+		}
+		return result;
+	}
 	
 	
 	
src/main/java/itn/let/mjo/msgdata/web/MjonMsgDataController.java
--- src/main/java/itn/let/mjo/msgdata/web/MjonMsgDataController.java
+++ src/main/java/itn/let/mjo/msgdata/web/MjonMsgDataController.java
@@ -2088,16 +2088,8 @@
 	    		
 	    	}
 
-	        long startTime = System.currentTimeMillis(); // 시작 시간 측정
-	        
 	    	List<AddrVO> resultAddrList = mjonMsgDataService.selectMsgAddrListAjax(addrVO);
 
-	        long endTime = System.currentTimeMillis(); // 종료 시간 측정
-
-	        long elapsedTime = endTime - startTime; // 소요 시간 계산
-	        System.out.println("소요 시간: " + elapsedTime + " 밀리초");
-	        
-	        
 	    	
 	    	modelAndView.addObject("resultAddrList", resultAddrList);
 	    	modelAndView.addObject("result", "success");
src/main/webapp/WEB-INF/jsp/web/kakao/msgdata/at/KakaoAlimtalkMsgDataView.jsp
--- src/main/webapp/WEB-INF/jsp/web/kakao/msgdata/at/KakaoAlimtalkMsgDataView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/kakao/msgdata/at/KakaoAlimtalkMsgDataView.jsp
@@ -7,6 +7,8 @@
 
 <!-- <script src="/publish/js/content.js"></script> -->
 <!-- 주소록관련 js -->
+
+
 <script type="text/javascript" defer src="<c:out value='/js/kakao/at/init.js' />"></script>
 <script type="text/javascript" src="<c:out value='/js/kakao/at/tabulator.js' />"></script>
 <script type="text/javascript" src="<c:out value='/js/kakao/at/addr.js' />"></script>
@@ -15,7 +17,6 @@
 <script type="text/javascript" src="<c:out value='/js/common/popup.js' />"></script>
 <script type="text/javascript">
 var loginVO = '${loginVO}';
-
 // 체크박스 동적 바인딩
 $(document).on('click','.wrap01C', function(){
 	var total = $(".wrap01C").length;
@@ -87,28 +88,52 @@
 	
 	//선택삭제 버튼 클릭 이벤트
 	$("#select_del").on('click', function(){
-		
-		if($('.wrap01C:checkbox:checked').length < 1)
-		{
-			alert("삭제할 연락처를 선택해주세요.!!");
+
+
+		if(tableL == null || tableL == ""){
+			
+			alert("받는사람을 추가해 주세요.");
 			return false;
+		
 		}
 		
-		$('.wrap01C').each(function(index, item){
-			if($(item).is(':checked'))
-				$(item).parent().parent().remove();
-		});
-		updateTotCnt();
+		var selectedData = tableL.getSelectedRows();
+		
+		if(selectedData == "" || selectedData == null){
+			
+			alert("삭제할 연락처를 선택해주세요.");
+			return false;
+			
+			// 선택한 Row 데이터 삭제하기
+		}else if(confirm("선택하신 받는 사람을 삭제하시겠습니까?")){
+				
+			// 선택 데이터 삭제
+			selectedData.forEach(row => row.delete());
+			
+			
+			totRows = tableL.getRows().length;
+			updateTotCnt(totRows);
+			
+			var smsTxtArea = $('#smsTxtArea').val();
+			
+			//일괄변환 문구 결제금액 처리
+			
+		}
+		
 	});
 
 	//선택삭제 버튼 클릭 이벤트
  	$("#all_del").on('click', function(){
-		
+
+        
 		if(!confirm("받는사람 목록을 모두 삭제하시겠습니까?"))
 			return false;
 		
 		$('#wrap01_body .list_body').remove();
 
+		tableL.clearData();
+		
+		
 		$('#rowTotCnt').text(0);
 		$('#rowDupCnt').text(0);
 	});
@@ -509,19 +534,13 @@
 		return false;
 	}
 	
-	//수신자 목록 체크
-	if($('.phoneArea').length < 1)
-	{
-		alert('받는 사람 입력 후 발송해 주세요');
-		return false;
-	}
 	
 	//수신자 목록 체크
-	if($('.phoneArea').length > 500)
+	/* if($('.phoneArea').length > 500)
 	{
 		alert("최대 발송 건수는 500건 입니다.");
 		return false;
-	}
+	} */
 	
 
 	if($('#errorChk').val() === 'N' 
@@ -626,7 +645,13 @@
 	}
 
 	
-	
+
+    //수신자 목록 체크
+    if(dataList.length < 1)
+    {
+        alert('받는 사람 입력 후 발송해 주세요');
+        return false;
+    }
 	
 	
 	// 채널 ID
@@ -768,22 +793,25 @@
 function fn_callToListParsing(){
 
     var dataList = [];
-	/* var callToList = [];
-	// excel body
-	$('.phoneArea').each(function(index, item){
-			callToList.push($(item).text().replaceAll('\\t', ''));
-	});
 	
-	$('#bizForm #callToList').val(callToList); */
-	
-	$('.phoneArea').each(function(index, item){
+/* 	$('.phoneArea').each(function(index, item){
         var row = {};
         var value = $(item).text().replaceAll('\\t', '')
         row["callToList"] = value; // 수신번호는 별도로 처리
         dataList.push(row);
     });
+	 */
+	   
+	// Tabulator 테이블의 데이터 가져오기
+	var tableData = tableL.getData(); 
 	
-    return dataList;
+	tableData.forEach(function(row){
+		var dataRow = {};
+		dataRow["callToList"] = row.phone; // phone 필드 값을 callToList로 저장
+		dataList.push(dataRow);
+	});
+	
+	return dataList;
 
 	
 
@@ -901,9 +929,9 @@
 	$('#smsLen').val(conLeng);
 	
 	
+	$('#msgLeng').html(conLeng + " / ");
 	if(conLeng > 90){
 		
-		$('#msgLeng').html(conLeng + " / ");
 		$('#limitLeng').html("2000");
 		$('.msg_com').html("장문");
 		$('#msgType').val("6"); // 메세지 타입 설정
@@ -917,7 +945,6 @@
 		
 	}else{
 		
-		$('#msgLeng').html(conLeng + " / ");
 		$('#limitLeng').html("90");
 		$('.msg_com').html("단문");
 		$('#msgType').val("4"); // 메세지 타입 설정
@@ -930,7 +957,7 @@
 	}
 		
 	//수신목록 전체 데이터 갯수 구하기
-	updateTotCnt(totRows);
+	//updateTotCnt(totRows);
 }
 
 /**
@@ -1318,25 +1345,25 @@
 													</span>
 												</div>
 												<div class="receipt_num_midde">
-													<div class="listType list01" >
-														<div class="list_table list_head">
+													<div class="listType list01 callList_box_P">
+														<!-- <div class="list_table list_head">
 															<div class="cb_wrap">
 																<label for="select_all" class="label"></label>
 																<input type="checkbox" id="select_all">
 															</div>
 															<div class="list_table_num">
 																<p>휴대폰</p>
-<!--                                                                 <img src="/publish/images/sortUp.png"> -->
-<!--                                                                 <img src="/publish/images/sortDown.png"> -->
+                                                                <img src="/publish/images/sortUp.png">
+                                                                <img src="/publish/images/sortDown.png">
 															</div>
-<!-- 															<div class="list_table_name"> -->
-<!-- 																<p>이름</p> -->
-<!--                                                                 <img src="/publish/images/sortUp.png"> -->
-<!--                                                                 <img src="/publish/images/sortDown.png"> -->
-<!-- 															</div> -->
+															<div class="list_table_name">
+																<p>이름</p>
+                                                                <img src="/publish/images/sortUp.png">
+                                                                <img src="/publish/images/sortDown.png">
+															</div>
 														</div>
 														<div class="list_body_wrap" id="wrap01_body">
-														</div>
+														</div> -->
 													</div>
 													<div class="put_right">
 														<div class="btn_popup_wrap spc_wrap">
@@ -1648,7 +1675,9 @@
 							<!--// table -->
 						</div>
 						<div class="popup_btn_wrap2">
-							<button type="button" onClick="javascript:addrToList(); return false;">추가</button>
+							<button type="button" onClick="javascript:addrToList_advc('all'); return false;">전체추가</button>
+							<button type="button" onClick="javascript:addrToList_advc('select'); return false;">선택추가</button>
+<!-- 							<button type="button" onClick="javascript:addrToList(); return false;">추가</button> -->
 							<button type="button" onClick="javascript:addrClose(); return false;">닫기</button>
 						</div>
 						<%-- 주소록 레이어 팝업 닫기 실행 코드 --%>
src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataSMLView.jsp
--- src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataSMLView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataSMLView.jsp
@@ -18,9 +18,6 @@
 
 $(document).ready(function(){
 
-	
-	console.log("12111111111111");
-
 // 	console.log(' + $(#tabDision).val() : ',$('#tabDision').val())
 // 	if($('#tabDision').val() == 'tab02'){
 // 		$('#send_adYnY').prop('checked', true); // 상태 변경 및 이벤트 발생
src/main/webapp/js/MJUtill.js
--- src/main/webapp/js/MJUtill.js
+++ src/main/webapp/js/MJUtill.js
@@ -200,6 +200,8 @@
 
 //숫자 천단위 콤마 찍어주기
 function numberWithCommas(x) { 
+    console.log(' + typeof x : ',typeof x);
+    console.log(' + x : ',x);
 	return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 
 }
 
@@ -1030,6 +1032,17 @@
 }
 
 
+function setAllCntData(result){
+
+    console.log('result :: ', result);
+    // 합쳐진 데이터를 tableL에 설정합니다.
+    tableL.setData(result.uniqueArray);
+    // 중복데이터 건수 입력
+    setRowDupCnt(result.duplicateCount);
+         
+    $('#rowTotCnt').text(result.uniqueCount)
+}
+
 function validateRowLimit(totalRows, limit = 300000) {
 	// 값과 타입 확인
 
src/main/webapp/js/kakao/at/addr.js
--- src/main/webapp/js/kakao/at/addr.js
+++ src/main/webapp/js/kakao/at/addr.js
@@ -101,6 +101,7 @@
 }
 
 //주소록 불러오기에서 수신자 리스트 추가해 주기
+/*
 function addrToList(){
 	
 	var selectedData = tableAddr.getSelectedRows();
@@ -114,10 +115,10 @@
 	}else{ // 선택한 Row 데이터 저장해주기
 		
 		// 선택한 Row 데이터 저장해주기
-		if(selectedData.length > 500){
-			alert("최대 발송 건수는 500 입니다.");
-			return false;
-		}else{
+//		if(selectedData.length > 500){
+//			alert("최대 발송 건수는 500 입니다.");
+//			return false;
+//		}else{
 			for(var i=0; i < selectedData.length; i++){
 				
 				//좌측 받는사람 리스트를 담아둔 배열에 데이터를 추가해 준다.
@@ -137,9 +138,67 @@
 			
 			//주소록 레이어 팝업의 Tabulator 데이터 지워주기
 			tableAddr.clearData();
-		}
+//		}
 	}
 	
+}
+*/
+
+//주소록 불러오기에서 수신자 리스트 추가해 주기
+function addrToList_advc(type){
+
+
+    // 선택된 데이터 또는 전체 데이터 변수 초기화
+    let selectedData = type === 'select' ? tableAddr.getSelectedRows() : tableAddr.getData();
+    
+    // 데이터가 비어있으면 경고 후 종료
+    if (!selectedData || selectedData.length < 1) {
+        
+        if(tableAddr.getDataCount() < 1){
+            alert("주소록을 선택해 주세요.");
+        }else{
+            alert("전화번호를 선택해 주세요.");
+        }
+        
+        return false;
+    }
+
+
+    // 데이터 변환 로직
+    const addrData = selectedData.map(row => {
+        const rowData = type === 'select' ? row.getData() : row; // 'select'는 행 객체에서 데이터 추출
+        return {
+            phone: removeDash(rowData.phone),
+        };
+    })
+    .filter(row => checkHpNum(row.phone)); // 유효한 번호만 필터링;
+
+
+    // 기존 tableL의 데이터를 가져옵니다.
+    var existingData = tableL.getData();
+    // 기존 데이터와 새로운 데이터를 합칩니다.
+    var combinedData = existingData.concat(addrData);
+
+    // @ phone을 기준으로 중복 제거 및 갯수 계산
+    const result = removeDuplicatesAndCount(combinedData, 'phone');
+
+
+
+    // 총 30만건이 넘으면 false
+    if (!validateRowLimit(result.uniqueCount)) {
+        return false;
+    }
+
+    setAllCntData(result);
+
+
+    // 미리보기 버튼 활성화
+
+
+    $(".closeAddr").trigger("click");
+    //주소록 레이어 팝업의 Tabulator 데이터 지워주기
+    tableAddr.clearData();
+    fn_priceClclt();
 }
 
 function loadAddrList(){
@@ -150,7 +209,8 @@
 	*/
 	var data = $("#searchAddrGrpForm").serialize();	 
 
-	var url = "/web/mjon/msgdata/selectMsgAddrListAjax.do";
+	// var url = "/web/mjon/msgdata/selectMsgAddrListAjax.do";
+    var url = "/web/mjon/msgdata/selectMsgAddrListAjax_advc.do";
 	
 	$.ajax({
         type: "POST",
@@ -159,43 +219,36 @@
         dataType:'json',
         async: false,
         cache: false,
-        success: function (returnData, status) {
-			console.log('returnData : ', returnData);
-			if(status == 'success'){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
-				if(returnData.result == "success"){
-					
-					var addrList = returnData.resultAddrList;
-					var tableData = [];
-					
-					if(addrList.length == 0){
-						
-						alert("주소록 정보가 없습니다.");
-						tableAddr.setData(tableData);
-						return false;
-					}
-					
-					//받는사람 리스트를 담아둔 배열에 신규 추가 데이터를 추가해 준다.
-					for(var i=0; i < addrList.length; i++){
-						
-						tableData.push({addrGroupNm: addrList[i].addrGrpNm, addrPhone: removeDash(addrList[i].addrPhoneNo) , addrName: addrList[i].addrNm, addrRep1: addrList[i].addrInfo1, addrRep2: addrList[i].addrInfo2, addrRep3: addrList[i].addrInfo3, addrRep4: addrList[i].addrInfo4});
-						
-					}
-					
-					//우측 주소록 리스트 Tabulator에 입력해주기
-					tableAddr.setData(tableData);
-					
-				}else{
-					
-					alert(returnData.message);
-					return false;
-					
-				}
-				
-			} else if(status== 'fail'){
-				alert("주소록 불러오기에 실패하였습니다. !!");
-			}
-		},
-        error: function (e) { alert("주소록 불러오기에 실패하였습니다."); console.log("ERROR : ", e); }
+        success: function (data) {
+        console.log('data : ', data);
+        if(data.status == "OK"){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
+            
+                var addrList = data.object;
+                
+                if(addrList.length == 0){
+                    
+                    alert("주소록 정보가 없습니다.");
+//                    tableAddr.setData([]);
+                    return false;
+                }
+                tableAddr.setData(addrList);
+        } 
+        else
+        {
+            alert("주소록 불러오기에 실패하였습니다. !!");
+        }
+        },
+        error: function (e) {
+             alert("주소록 불러오기에 실패하였습니다."); console.log("ERROR : ", e); 
+         }
+        , beforeSend : function(xmlHttpRequest) {
+                    //로딩창 show
+            $('.loading_layer').addClass('active');             
+        }
+        , complete : function(xhr, textStatus) {
+            //로딩창 hide
+            $('.loading_layer').removeClass('active');
+        }
     });
 	
 }
src/main/webapp/js/kakao/at/alimtalkExcel.js
--- src/main/webapp/js/kakao/at/alimtalkExcel.js
+++ src/main/webapp/js/kakao/at/alimtalkExcel.js
@@ -198,6 +198,7 @@
  * 
  * */
 function excelExportVarAjax(){
+    console.log(' :: excelExportVarAjax ::')
 	
 	var data = document.getElementById('excelFile').files;
 	
@@ -245,7 +246,7 @@
         //timeout: 600000,
         success: function (returnData, status) {
 			if(status == 'success'){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
-				
+				console.log('returnData : ', returnData);
 				if(returnData.success){
 					
 					var data = returnData.data;
@@ -290,10 +291,10 @@
 						
 						var totalDuplCnt = $('#rowDupCnt').text();//중복 건수 정보
 						var $excelBody = $('#excelBody02');
-						var bodyData;
 						var addDiv = "";
 						var phoneNum;
 
+                        var msgCnt = 0;
 						//입력데이터를 역정렬해준다.
 						data.reverse();
 
@@ -337,6 +338,7 @@
 									
 									if(lengthCheck){
 										addDiv += excelBody;
+                                        msgCnt++;
 									}
 									
 								}
@@ -359,6 +361,9 @@
 						
 						//화면에 수신번호 및 변수 데이터 추가해 주기
 						$excelBody.append(addDiv);
+                        
+                        // 총 금액 계산
+                        fn_priceClclt(msgCnt);
 
 					}
 					
src/main/webapp/js/kakao/at/init.js
--- src/main/webapp/js/kakao/at/init.js
+++ src/main/webapp/js/kakao/at/init.js
@@ -12,6 +12,8 @@
  * 
  * 
  */
+
+
 $(document).ready(function(){
 
 
src/main/webapp/js/kakao/at/priceClclt.js
--- src/main/webapp/js/kakao/at/priceClclt.js
+++ src/main/webapp/js/kakao/at/priceClclt.js
@@ -70,8 +70,6 @@
 		fn_priceClclt();
 	});
 
-	
-
 
 	// 대상 노드에 감시자 전달
 	observer_wrap01.observe(target01, option);
@@ -85,18 +83,24 @@
 /**
  * @description 금액 계산 function
  */
-function fn_priceClclt(){
+function fn_priceClclt(phoneSu = 0){
 
-	// 미리보기 텍스트
-	var templateHtml = $('#smsTxtArea').val();
-	// var templateHtml = $('.template_text').html();
-	
-	// 수신 번호 개수
-	var phoneSu = $('.phoneArea').length;
+    console.log(":: at fn_priceClclt :: ");
+    // 미리보기 텍스트
+    var templateHtml = $('#smsTxtArea').val();
+    // var templateHtml = $('.template_text').html();
+    
+    // 치환문자 여부 확인
+    var txtReplYn = $('#txtReplYn').val()
+    console.log('txtReplYn : ', txtReplYn);
+    
+    // 수신 번호 개수
+    if(phoneSu == 0 && txtReplYn == 'Y')
+    	phoneSu = $('.phoneArea').length;
+    if(phoneSu == 0 && txtReplYn == 'N')
+    	phoneSu = tableL.getData().length;
 	// 대체문자 있는지 확인
 	var isSendFailChecked = $("#send_fail_check").is(":checked");
-	// 치환문자 여부 확인
-	var txtReplYn = $('#txtReplYn').val()
 	// 대체문자 하위에 장문 / 단문 select
 	var msgTypeText = $('.msg_com').text().trim();
 
@@ -212,8 +216,8 @@
 	if(msgTypeText == '단문') price = SHORT_PRICE * phoneSu;
 	else if(msgTypeText == '장문') price = LONG_PRICE * phoneSu;
 	else  price = KAKAO_AT_PRICE * phoneSu; // 카카오
-
-	$('#totalPriceTxt').text((price).toFixed(1));
+    
+	$('#totalPriceTxt').text(numberWithCommas((price).toFixed(1)));
 }
 
 
src/main/webapp/js/kakao/at/tabulator.js
--- src/main/webapp/js/kakao/at/tabulator.js
+++ src/main/webapp/js/kakao/at/tabulator.js
@@ -12,83 +12,41 @@
  * 
  * 
  */
-
+var tableL = null;
 $(document).ready(function (){
-	/**
 	//받는사람 연락처 내용 처리
 	//Tabulator AJAX Data Loading
-	tableL = new Tabulator(".callList_box", {
+	tableL = new Tabulator(".callList_box_P", {
 		height:"255px",
-	    layout:"fitColumns",
-	    //data:tabledata,
-	    //autoColumns:true,
+        layout:"fitColumns",
+        headerHozAlign:"center",
+        validationMode:"highlight",
 	    headerHozAlign:"center",
 	    validationMode:"highlight",
-	    //clipboard:false,
-	    //clipboardCopySelector:"table",
-	    //clipboardPasteAction:"insert", // insert, update, replace
 	    placeholder:"복사(Ctrl+C)한 내용을 여기에 붙여넣기(Ctrl+V) 해주세요.", //fit columns to width of table (optional)
 	    resizableColumns:false,
+        columnDefaults:{ // 공통설정
+            hozAlign: "center",
+            headerHozAlign: "center",
+            editor: "input",
+            editor: false
+        },
 	 	columns:[ //Define Table Columns
-	 		{formatter:"rowSelection", titleFormatter:"rowSelection",clipboard:false, hozAlign:"center", headerSort:false, cellClick:function(e, cell){
+	 		{formatter:"rowSelection", titleFormatter:"rowSelection", width:60, clipboard:false, hozAlign:"center", headerSort:false, cellClick:function(e, cell){
 	 	        cell.getRow().toggleSelect();
 		 	}}, 
-		 	{title:"이름", hozAlign:"center", field:"name", editor:"input", validator:["maxLength:12"], cellEdited:function(cell){
-		 	    //cell - cell component
-		 	    fnReplCell();
-		    }},
-		 	{title:"휴대폰", hozAlign:"center", field:"phone", editor:"input", width:100, validator:["required","minLength:10", "maxLength:12"], cellEdited:function(cell){
+//		 	{title:"이름", hozAlign:"center", field:"name", editor:"input", validator:["maxLength:12"], cellEdited:function(cell){
+//		 	    //cell - cell component
+//		 	    fnReplCell();
+//		    }},
+		 	{title:"휴대폰", hozAlign:"center", field:"phone", editor:"input", validator:["required","minLength:10", "maxLength:12"], cellEdited:function(cell){
 		 	    //cell - cell component
 		 	    fnDuplPhone();
 		    }},
-		 	{title:"[*1*]", hozAlign:"center", field:"rep1", editor:"input", minWidth:60, validator:["maxLength:40"], cellEdited:function(cell){
-		 	    //cell - cell component
-		 	    fnReplCell();
-		    }},
-		 	{title:"[*2*]", hozAlign:"center", field:"rep2", editor:"input", minWidth:60, validator:["maxLength:40"], cellEdited:function(cell){
-		 	    //cell - cell component
-		 		fnReplCell();
-		    }},
-		 	{title:"[*3*]", hozAlign:"center", field:"rep3", editor:"input", minWidth:60, validator:["maxLength:40"], cellEdited:function(cell){
-		 	    //cell - cell component
-		 		fnReplCell();
-		    }},
-		 	{title:"[*4*]", hozAlign:"center", field:"rep4", editor:"input", minWidth:60, validator:["maxLength:40"], cellEdited:function(cell){
-		 	    //cell - cell component
-		 		fnReplCell();
-		    }},
-		 	
-	 	],
-	 	validationFailed:function(cell, value, parameters){ // 유효성 체크 함수 - 아직 잘 모르겠음 
-	        //take action on validation fail
-	 		var valid = cell.isValid();
-	 		var fieldNm = cell.getField();
-	 		var cellVal = cell.getValue();
-	 		var returnVal = "";
-	 		if(!valid){
-	 			
-	 			if(fieldNm == "name"){
-	 				alert("받는사람 이름은 최대 12글자까지만 입력 가능합니다.");
-	 				cell.setValue(strMaxLengthSubstring(cellVal, 11)); //스크립트 함수가 0부터 시작이므로 원하는 글자수 -1을 해줘야한다.
-	 				cell.clearValidation();
-	 			}else if(fieldNm == "phone"){
-	 				alert("휴대폰번호는 하이픈(-)을 제외한 숫자만 정확히 입력해 주세요.");
-	 			}else{
-		 			alert("치환문자를 정확히 입력해 주세요. 40글자 이내로 입력 가능합니다.");
-		 			cell.setValue(strMaxLengthSubstring(cellVal, 39));
-		 			cell.clearValidation();
-	 			}
-	 			
-	 			//해당 셀 데이터 삭제
-	 			//cell.setValue("");
-	 			
-	 		}
-	 		return value % parameters.phone;
-	    },
+	 	]
 
 	});
 	
-	 */
 	//주소록 불러오기 팝업 내용
 	//Tabulator AJAX Data Loading
 	tableAddr = new Tabulator(".callAddr_box", {
@@ -104,8 +62,8 @@
 		 		}
 		 	}, 
 		 	{title:"그룹명", hozAlign:"center", field:"addrGroupNm", widthGrow: 3, editor:"input", validator:["required","minLength:2", "maxLength:40"]},
-		 	{title:"이름", hozAlign:"center", field:"addrName", widthGrow: 2, editor:"input", validator:["maxLength:12"]},
-		 	{title:"휴대폰번호", hozAlign:"center", field:"addrPhone", widthGrow: 3, editor:"input", validator:["required","minLength:10", "maxLength:11"]},
+		 	{title:"이름", hozAlign:"center", field:"name", widthGrow: 2, editor:"input", validator:["maxLength:12"]},
+		 	{title:"휴대폰번호", hozAlign:"center", field:"phone", widthGrow: 3, editor:"input", validator:["required","minLength:10", "maxLength:11"]},
 		 	
 	 	],
 	 	validationFailed:function(cell, value, parameters){ // 유효성 체크 함수 - 아직 잘 모르겠음 
@@ -132,53 +90,32 @@
 	//받는사람 번호 버튼 클릭시 Tabulator에 데이터 넣어주기
 	$('.addCallToF').click(function(){ 
 		
-		var callToNum = $('#callTo').val();
-		if(callToNum == null || callToNum == ""){
-			
-			alert("받는사람 번호를 입력해 주세요.");
-			return false;
-			
-		}else if(!checkHpNum(callToNum)){	
-			
-			alert("올바른 전화번호를 입력해 주세요.");
-			$('#callTo').val("");
-			return false;
-		}
-		
-		//핸드폰 번호에 '-' 문자 제거하기
-		callToNum = removeDash(callToNum);
+        // checkHpNum(callToNum)
+        
+        
 
-		  var dpCnt = 0;
-		$(".phoneArea p").each(function(index, item){
-			if(this.textContent == callToNum){
-				
-				dpCnt++;
-				
-				alert("받는사람 리스트에 동일한 연락처가 있습니다.");
-		    	$('#callTo').val("");
-		    	return false;
-			}
-		});
-		
-		if(dpCnt > 0){
-			alert("받는사람 리스트에 동일한 연락처가 있습니다.");
-			$('#callTo').val("");
-			return false;
-		}else{
-			// fn_displayJsonToHtmlTable 재사용을 위한
-			// 파라미터 형변환
-			var data =[];
-			data.push({ phone: callToNum, name: ''});
-			fn_displayJsonToHtmlTable(data);
-			
-			/*
-			 * 토탈 카운트 화면에 노출
-			 */ 
-			updateTotCnt();	
-			
-			// 번호 추가란 초기화
-			$('#callTo').val('');
-		}
+        var textarea = $('#callTo');
+        const numbers = textarea.val().split('\n')
+                                .map(num => removeDash(num.trim()))
+                                .filter(num => num !== "")
+                                .filter(num => checkHpNum(num)); // 유효한 번호만 필터링;
+                                
+                                
+        
+        const formattedData = numbers.map(num => ({phone: num}));
+
+        // 기존 tableL의 데이터를 가져옵니다.
+        var existingData = tableL.getData();
+        // 기존 데이터와 새로운 데이터를 합칩니다.
+        var combinedData = existingData.concat(formattedData);
+
+        // @ phone을 기준으로 중복 제거 및 갯수 계산
+        const result = removeDuplicatesAndCount(combinedData, 'phone');
+        
+        setAllCntData(result);
+        
+        textarea.val('');
+        fn_priceClclt();
 	});
 	
 	
@@ -442,9 +379,9 @@
 * 토탈 카운트 화면에 노출
 * 변수 없는 리스트만 체크
 */ 
-function updateTotCnt(){
+function updateTotCnt(cnt){
 	
-	$("#rowTotCnt").text($('#wrap01_body .list_body').length);
+	$("#rowTotCnt").text(cnt);
 
 }
 
@@ -456,10 +393,10 @@
 		return;
 	}
 	
-	if(taData.length  > 500){
-		alert("최대 발송 건수는 500건 입니다.");
-		return;
-	}
+//	if(taData.length  > 500){
+//		alert("최대 발송 건수는 500건 입니다.");
+//		return;
+//	}
 	
 	
 	/*
Add a comment
List