이호영 이호영 2025-04-25
친구톡 발송 속도 개선중
@81d31fc55f0c554306ece85c544c50b752c8bca5
src/main/java/itn/let/kakao/admin/kakaoAt/web/MjonKakaoATController.java
--- src/main/java/itn/let/kakao/admin/kakaoAt/web/MjonKakaoATController.java
+++ src/main/java/itn/let/kakao/admin/kakaoAt/web/MjonKakaoATController.java
@@ -796,9 +796,16 @@
 				String lastUpdtPnttm = resultChannelList.get(i).getLastUpdtPnttm();
 				kakaoProfileVO.setSenderKey(senderKey); 
 				kakaoProfileVO.setProfileId(profileId);
-				
-				KakaoReturnVO tmpProfileVO = kakaoApiProfile.kakaoApiProfileList(kakaoProfileVO);
-				
+				KakaoReturnVO tmpProfileVO = null;
+//				try {
+//
+//					tmpProfileVO = kakaoApiProfile.kakaoApiProfileList(kakaoProfileVO);					
+//				} catch (Exception e) {
+//					e.printStackTrace();
+//					// TODO: handle exception
+//				}
+
+				tmpProfileVO = kakaoApiProfile.kakaoApiProfileList(kakaoProfileVO);			
 				ChannelIDVO returnChannelVO = new ChannelIDVO();
 				
 				returnChannelVO.setSenderKey(tmpProfileVO.getSenderKey());
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
@@ -83,7 +83,7 @@
 			"\n , msgType=[" + msgType + "]" +
 			"\n , templateContent=[" + templateContent + "]" +
 			"\n , templateTitle=[" + templateTitle + "]" +
-			"\n , buttonList=[" + buttonList.toString() + "]" +
+			"\n , buttonList=[" + (buttonList != null ? buttonList.toString() : "") + "]" +
 			"\n , subMsgSendYn=[" + subMsgSendYn + "]" +
 			"\n , subMsgTxt=[" + subMsgTxt + "]" +
 			"\n , subMsgType=[" + subMsgType + "]" +
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
@@ -1,6 +1,8 @@
 package itn.let.kakao.kakaoComm;
 
 import java.io.UnsupportedEncodingException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -28,6 +30,7 @@
 import itn.com.cmm.util.StringUtil;
 import itn.let.kakao.kakaoComm.kakaoApi.KakaoApiJsonSave;
 import itn.let.kakao.kakaoComm.kakaoApi.KakaoApiTemplate;
+import itn.let.kakao.user.kakaoAt.service.impl.KakaoAlimTalkDAO;
 import itn.let.mail.service.StatusResponse;
 import itn.let.mjo.mjocommon.MjonCommon;
 import itn.let.mjo.msg.service.MjonMsgVO;
@@ -44,6 +47,9 @@
 
 	@Autowired
 	KakaoApiJsonSave kakaoApiJsonSave;
+
+	@Resource(name="kakaoAlimTalkDAO")
+	private KakaoAlimTalkDAO kakaoAlimTalkDAO;
 	
 	@Resource(name = "MjonMsgDataService")
 	private MjonMsgDataService mjonMsgDataService;
@@ -56,6 +62,7 @@
 
 	@Autowired
 	private MjonCommon mjonCommon;
+	
 	
 	// 클래스 수준에서 정적 Pattern 정의 (성능 최적화)
 	private static final Pattern REPLACEMENT_PATTERN = Pattern.compile("#\\{[^}]+\\}");
@@ -142,7 +149,7 @@
 			log.info("");
 			
 /** @공통 기본값 */		
-			KakaoSendAdvcVO sendVO = createSendVO(kakaoVO);
+			KakaoSendAdvcVO sendVO = createATSendVO(kakaoVO);
 			String msgId = idList.get(i);
 			sendVO.setMsgId(msgId);
 
@@ -293,33 +300,16 @@
 		//사용자 현재 보유 금액 불러오기(문자 발송 금액 차감 이전 금액)
 //		String befCash = kakaoVO.getBefCash();
 		
-		
+		log.info(" [{}]", kakaoVO.ftToString());		
 		
 		List<KakaoSendAdvcVO> kakaoSendAdvcListVO = new ArrayList<>();
 		Calendar calendar = setupBaseDate(kakaoVO, isNotified);
 		
 		
-		
-		String templateContent = kakaoVO.getTemplateContent(); // 친구톡 내용
-//		kakaoVO.setTemplateContent(templateContent);
-//		String templateTitle = templateDetail.getTemplateTitle();
-		
-		
-//		log.info(" + templateDetail :: [{}]", templateDetail);
-//		templateDetail.getButtonList().forEach(t->log.info(" + ButtonList :: [{}]", t.toString()));
-		
-		Boolean hasContentReplacement = this.replBooleanStrChecker(templateContent);
-//		Boolean hasTitleReplacement = this.replBooleanStrChecker(templateTitle);
-//		Boolean hasButtonReplacement = this.needsButtonReplacement(templateDetail.getButtonList());
-		
-		/** @jsonStr 필요유무 */
-		boolean hasTitleOrButtons = CollectionUtils.isNotEmpty(kakaoVO.getButtonVOList());
-		
-		/** @jsonStr 반복유무 */
-//		boolean needsJsonReplacement = hasTitleReplacement || hasButtonReplacement;
-		String sharedJsonStr = null;
-		
-		String subMsgTxt = kakaoVO.getSubMsgTxt(); // 실패 대체 문자
+		 // 친구톡 내용
+		String templateContent = kakaoVO.getTemplateContent();
+		 // 실패 대체 문자
+		String subMsgTxt = kakaoVO.getSubMsgTxt();
 		
 		// 시스템 기본 단가 정보 불러오기
 		JoinSettingVO sysJoinSetVO = mjonMsgDataService.selectJoinSettingInfo();
@@ -327,14 +317,15 @@
 		MberManageVO mberManageVO = mjonMsgDataService.selectMberManageInfo(kakaoVO.getUserId());
 		
 
-		String smsTxtTemp = templateContent;
+		// 치환 문구가 있는지 확인
 		Boolean replaceYN = MsgSendUtils.getReplaceYN(templateContent);
+		Boolean replaceSubYN = MsgSendUtils.getReplaceYN(subMsgTxt);
 
 		boolean hasPerformedMsgType = false; // 치환 문자가 없는 경우, 스팸 체크가 한 번만 수행되도록 제어
 		
 		
 		/** @MSGID KEY값 */
-		List<String> idList = mjonCommon.getNextCustomMsgCId(kakaoVO.getVarListMap().size());
+		List<String> idList = mjonCommon.getNextCustomMsgCId(kakaoVO.getMjonFTSendVOList().size());
 
 
 		Map<String, Function<MjonFTSendVO, String>> placeholders = new HashMap<>();
@@ -345,97 +336,114 @@
 		placeholders.put("[*4*]", MjonFTSendVO::getRep4);
 		
 
+		// 친구통 금액 
+		Float kakaoFtPrice = mberManageVO.getKakaoFtPrice();
+		
+		// 대체문자가 있을경우 사용 
+		float shortPrice = getValidPrice(mberManageVO.getShortPrice(), sysJoinSetVO.getShortPrice());
+		float longPrice = getValidPrice(mberManageVO.getLongPrice(), sysJoinSetVO.getLongPrice());
+		
+		String shortPStr = Float.toString(shortPrice);
+		String mmsPStr = Float.toString(longPrice);
 
-		String msgTypeResult = null;
+
+		/** @jsonStr 필요유무 */
+		boolean hasTitleOrButtons =  CollectionUtils.isNotEmpty(kakaoVO.getButtonVOList());
+		String sharedJsonStr = null;
+		
+		
+
 		List<MjonFTSendVO> mjonFTSendVOList = kakaoVO.getMjonFTSendVOList();
 
 		// 분할 건수 카운터
 		int counter = 0;  
-		for (MjonFTSendVO sendVO : mjonFTSendVOList) {
-			KakaoSendAdvcVO kakaoSendAdvcVO = new KakaoSendAdvcVO();
-			
-			kakaoSendAdvcVO.setCallFrom(kakaoVO.getCallFrom());
-			kakaoSendAdvcVO.setCallTo(sendVO.getPhone());
-			kakaoSendAdvcVO.setUserId(kakaoVO.getUserId());
+		for (int i = 0; i < mjonFTSendVOList.size(); i++) {
+			MjonFTSendVO mjonFTSendVO = mjonFTSendVOList.get(i);
 
-			String smsTxt = smsTxtTemp;
+			KakaoSendAdvcVO sendVO = createFTSendVO(kakaoVO, calendar);
+			// 공통 가격 설정
+			sendVO.setSmsPrice(shortPStr);
+			sendVO.setMmsPrice(mmsPStr);
+
+			sendVO.setCallTo(mjonFTSendVO.getPhone());
+			sendVO.setMsgId(idList.get(i));
+			
+			String smsTxt = templateContent;
 			// 치환 문자면
 			if(replaceYN) {
 				
 				// 각 치환 구문을 확인하고 치환할 값이 없으면 오류 반환
 				for (Map.Entry<String, Function<MjonFTSendVO, String>> entry : placeholders.entrySet()) {
 					String placeholder = entry.getKey();
-					String value = entry.getValue().apply(sendVO);
-//					log.info(" + smsTxtTemp [{}]", smsTxtTemp);
-//					log.info(" + placeholder [{}]", placeholder);
-//					log.info(" + value [{}]", value);
-//					log.info(" + smsTxtTemp.contains(placeholder) [{}]", smsTxtTemp.contains(placeholder));
+					String value = entry.getValue().apply(mjonFTSendVO);
 					if (smsTxt.contains(placeholder)) {
 						if (StringUtils.isEmpty(value)) {
 							statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "치환 문구중 " + placeholder + " 데이터가 없습니다.");
-							return false;
+							return null;
 						}
 						smsTxt = smsTxt.replace(placeholder, value);
-//						log.info(" + smsTxt [{}]", smsTxt);
-						
 					}
 				}
 			}
-
-			String smsSpamChkTxt = smsTxt;
-			if(StringUtils.isNotEmpty(smsTxt)) {
-				smsSpamChkTxt = smsTxt.replaceAll(String.valueOf((char) 13), "");
-			}
-
+			sendVO.setTemplateContent(smsTxt);
 			
 			
-			// == 치환 여부에 따라 처리 로직 분기 == 
-			// 치환 문자가 아닌 경우
-			if (!replaceYN) {
-				if (!hasPerformedMsgType) {
- 					msgTypeResult = getMsgTypeWithByteValidation(sendVO, smsTxt);
-					if ("INVALID".equals(msgTypeResult)) {
-						statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다.");
-						return null;
+			
+			String subMsgTxtTemp = null;
+			
+			if(StringUtils.isNotEmpty(subMsgTxt)) {
+				subMsgTxtTemp = subMsgTxt;
+				
+				// 각 치환 구문을 확인하고 치환할 값이 없으면 오류 반환
+				for (Map.Entry<String, Function<MjonFTSendVO, String>> entry : placeholders.entrySet()) {
+					String placeholder = entry.getKey();
+					String value = entry.getValue().apply(mjonFTSendVO);
+					if (subMsgTxtTemp.contains(placeholder)) {
+						if (StringUtils.isEmpty(value)) {
+							statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "치환 문구중 " + placeholder + " 데이터가 없습니다.");
+							return null;
+						}
+						subMsgTxtTemp = subMsgTxtTemp.replace(placeholder, value);
 					}
-					hasPerformedMsgType = true;
-				}
-			} 
-			else  
-			{// 치환 문자인 경우
-
-				// 메시지 타입 체크는 매번 수행
-				msgTypeResult = getMsgTypeWithByteValidation(sendVO, smsTxt);
-				if ("INVALID".equals(msgTypeResult)) {
-					statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다.");
-					return null;
 				}
 			}
+			sendVO.setSubMsgTxt(subMsgTxtTemp);
 			
 			
-			
-			
-			kakaoSendAdvcVO.setTemplateContent(smsTxt);
-			kakaoSendAdvcVO.setMsgType(msgTypeResult);
-			
-			
+			//대체문자가 있으면
+			// Step 1-4: 실패 대체 문자 치환데이터 설정
+			if(StringUtils.isNotEmpty(subMsgTxtTemp)) { // 대체문자가 있나?
+				int smsTxtByte = mjonCommon.getSmsTxtBytes(subMsgTxtTemp);
+				String sendType = getMsgType(smsTxtByte);
+				sendVO.setSubMsgType(sendType);
 
-			
-			// 예약 여부 확인
-			if ("Y".equalsIgnoreCase(kakaoVO.getReserveYn())  
-				&& "Y".equalsIgnoreCase(kakaoVO.getDivideChk())  
-				&& counter == Integer.parseInt(kakaoVO.getDivideCnt())) 
-			{
-				counter = 0;
-				calendar.add(Calendar.MINUTE, Integer.parseInt(kakaoVO.getDivideTime()));
+				if ("INVALID".equals(sendType)) {
+					statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "전송 문자 길이를 초과하였습니다.");return kakaoSendAdvcListVO;
+				}
+	
+				boolean isMms = "MMS".equals(sendType);
+				sendVO.setEachPrice(isMms ? mmsPStr : shortPStr);
+				sendVO.setSubMsgTxt(subMsgTxt);// 실패 
+			}else {
+				kakaoFtPrice = getValidPrice(mberManageVO.getKakaoAtPrice(), sysJoinSetVO.getKakaoAtPrice());
+				sendVO.setEachPrice( Float.toString(kakaoFtPrice) );
 			}
-			counter++;
-			// 즉시 발송인경우 현재 시간 
-			// 예약인 경우 위에 설정한 시간 입력
-			kakaoSendAdvcVO.setReqDate(DATE_FORMATTER.format(calendar.getTime())); 
 
-			kakaoSendAdvcListVO.add(kakaoSendAdvcVO);
+
+			// 타이틀과 버튼이 있고
+			if(hasTitleOrButtons) {
+				// 
+				if (StringUtils.isEmpty(sharedJsonStr)) {
+					// 치환 데이터가 없고 아직 생성되지 않았으면 한 번만 생성
+					sharedJsonStr = kakaoApiJsonSave.kakaoApiFTJsonSave_advc(kakaoVO);
+					sendVO.setJsonStr(sharedJsonStr);
+				}
+				sendVO.setBizJsonName(idList.get(0));
+				
+			}
 			
+			kakaoSendAdvcListVO.add(sendVO);
+			log.info(" sendVO.toString() :: [{}]",sendVO.toString());
 		}
 		
 		
@@ -508,14 +516,40 @@
 	 * @return
 	 * 
 	 */
-	private KakaoSendAdvcVO createSendVO(KakaoVO kakaoVO) {
+	private KakaoSendAdvcVO createATSendVO(KakaoVO kakaoVO) {
 		KakaoSendAdvcVO sendVO = new KakaoSendAdvcVO();
 		sendVO.setMsgType("8");
+		sendVO.setAgentCode("04");
 		sendVO.setSenderKey(kakaoVO.getSenderKey());
 		sendVO.setTemplateCode(kakaoVO.getTemplateCode());
 		sendVO.setUserId(kakaoVO.getUserId());
 		sendVO.setCallFrom(kakaoVO.getCallFrom());
+		return sendVO;
+	}
+	
+	
+	/**
+	 * @methodName	: createFTSendVO 
+	 * @author		: 이호영
+	 * @date		: 2025. 4. 23.
+	 * @description	: 
+	 * @return : KakaoSendAdvcVO
+	 * @param kakaoVO
+	 * @return
+	 * 
+	 */
+	private KakaoSendAdvcVO createFTSendVO(KakaoVO kakaoVO, Calendar calendar) {
+		KakaoSendAdvcVO sendVO = new KakaoSendAdvcVO();
+
+		sendVO.setMsgType("9"); // 알림톡 8 친구톡 9
 		sendVO.setAgentCode("04");
+		// 발송시간 : 친구톡은 분할 발송이 없어 처음 vo 생성 시 입력
+		sendVO.setReqDate(DATE_FORMATTER.format(calendar.getTime())); 
+		
+		sendVO.setSenderKey(kakaoVO.getSenderKey());
+		sendVO.setTemplateCode(kakaoVO.getTemplateCode());
+		sendVO.setUserId(kakaoVO.getUserId());
+		sendVO.setCallFrom(kakaoVO.getCallFrom());
 		return sendVO;
 	}
 	
@@ -1401,4 +1435,165 @@
 		statusResponse.setMessage(msg);
 		
 	}
+	
+	
+	// 보유 금액이 충분한지 확인하는 메서드
+	public 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.getEachPrice()))) // 변환 오류 방지
+			.reduce(BigDecimal.ZERO, BigDecimal::add)
+			.setScale(2, RoundingMode.HALF_EVEN); // 일관성 유지
+
+		// 비교 수행
+		return befCash.compareTo(totalEachPrice) >= 0;
+	}
+	
+	
+
+	/**
+	 * @methodName	: insertKakaoAtDataJsonInfo_advc 
+	 * @author		: 이호영
+	 * @date		: 2025. 4. 24.
+	 * @description	: INSERT INTO BIZ_ATTACHMENTS
+	 * @return : void
+	 * @param kakaoSendAdvcListVO
+	 * 
+	 */
+	public void insertKakaoAtDataJsonInfo_advc(List<KakaoSendAdvcVO> kakaoSendAdvcListVO) {
+
+		List<KakaoSendAdvcVO> jsonInfoData = new ArrayList<>(kakaoSendAdvcListVO);
+		jsonInfoData.removeIf(t -> StringUtils.isBlank(t.getJsonStr()));
+		log.info(" + jsonInfoData Insert :: [{}]", jsonInfoData.size());
+		if(jsonInfoData.size() > 0) {
+			kakaoAlimTalkDAO.insertKakaoAtDataJsonInfo_advc(jsonInfoData);
+		}
+
+	}
+	
+	
+
+	/**
+	 * @methodName	: insertKakaoData_advc 
+	 * @author		: 이호영
+	 * @date		: 2025. 3. 20.
+	 * @description	: 카카오 batch 발송 => mj_msg_data
+	 * @return : int
+	 * @param kakaoSendAdvcVOList
+	 * @param parentLoopCount
+	 * @param isJsonNotEmpty
+	 * @param isJsonNameAllSame
+	 * @return
+	 * 
+	 */
+	public int insertKakaoData_advc(List<KakaoSendAdvcVO> kakaoSendAdvcVOList) {
+
+
+		// 시작 시간 측정
+		long totalStartTime = System.currentTimeMillis();
+
+		int totalSize = kakaoSendAdvcVOList.size(); // 총 데이터 개수
+		// Batch 크기 설정 (고정값)
+//		int batchSize = 10000; 465
+		int batchSize = 50000; // 9분 18초
+
+		log.info("총 데이터 개수 :: [{}] ", totalSize);
+		log.info("설정된 Batch 크기 :: [{}] ", batchSize);
+
+		// 총 insert 카운트
+		int instCnt = 0;
+		int batchCount = 0;
+
+		// 각 배치별 실행 시간 기록
+		List<Double> batchExecutionTimes = new ArrayList<>();
+
+	
+		// 첫 번째 배치에서만 삽입했는지 추적하는 플래그
+		for (int i = 0; i < totalSize; i += batchSize) {
+			// Batch 시작 시간 측정
+			long batchStartTime = System.currentTimeMillis();
+
+			// Batch 리스트 생성
+			List<KakaoSendAdvcVO> batchList = kakaoSendAdvcVOList.subList(i, Math.min(i + batchSize, totalSize));
+			System.out.println("Batch 시작 인덱스: " + i);
+
+			// mj_msg_data 테이블 insert
+			int insertedCount = kakaoAlimTalkDAO.insertKakaoAtDataInfo_advc(batchList);
+			
+			/** @kakaoSendUtil.populateSendLists  
+			 * 하단에서 
+			 * getJsonStr 데이터 처리 후 활용
+			 *  */
+			instCnt += insertedCount;
+
+			// Batch 종료 시간 측정 및 실행 시간 계산
+			long batchEndTime = System.currentTimeMillis();
+			double batchExecutionTimeInSeconds = (batchEndTime - batchStartTime) / 1000.0;
+
+			// 실행 시간 기록
+			batchExecutionTimes.add(batchExecutionTimeInSeconds);
+			batchCount++;
+		}
+
+		// 종료 시간 측정
+		long totalEndTime = System.currentTimeMillis();
+
+		// 총 실행 시간 계산 (밀리초 -> 초로 변환)
+		double totalExecutionTimeInSeconds = (totalEndTime - totalStartTime) / 1000.0;
+
+		// 실행 시간 출력
+		log.info("총 배치 실행 횟수 :: [{}] ", batchCount);
+		log.info("batchSize :: [{}] ", batchSize);
+		log.info("총 실행 시간 :: [{}] ", totalExecutionTimeInSeconds + "초");
+		log.info("총 삽입 건수 :: [{}] ", instCnt);
+
+		// 각 배치별 실행 시간 출력
+		for (int k = 0; k < batchExecutionTimes.size(); k++) {
+			System.out.println("배치 " + (k + 1) + " 실행 시간 :: " + batchExecutionTimes.get(k) + "초");
+		}
+
+		return instCnt;
+		
+	}
+	
+	
+	
+	
+
+
+	public void insertKakaoGroupDataTb_advc(int instCnt, KakaoVO kakaoVO, KakaoSendAdvcVO sendVO) throws Exception {
+		// TODO Auto-generated method stub
+
+//		log.info(" + insertKakaoGroupDataTb_advc kakaoVO :: \n[{}]", kakaoVO.toString());;
+//		log.info(" + insertKakaoGroupDataTb_advc kakaoSendAdvcVOList :: \n[{}]", sendVO.toString());
+		
+		sendVO.setMsgGroupCnt(Integer.toString(instCnt));
+		sendVO.setReserveYn(kakaoVO.getReserveYn());
+		sendVO.setBefCash(priceAndPoint.getBefCash(sendVO.getUserId()));
+		sendVO.setBefPoint(priceAndPoint.getBefPoint(sendVO.getUserId()));
+		
+		Float eachPrice = Float.parseFloat(sendVO.getEachPrice());
+
+		Float totPrice = eachPrice * instCnt;
+		sendVO.setTotPrice(String.format("%.1f", totPrice));
+		
+		sendVO.setAtDelayYn(kakaoVO.getAtSmishingYn()); 
+		sendVO.setBizKakaoResendOrgnlTxt(kakaoVO.getSubMsgTxt());
+		sendVO.setBizKakaoResendType(sendVO.getSubMsgType());
+		
+		kakaoAlimTalkDAO.insertKakaoGroupDataTb_advc(sendVO);
+		
+	}
+	
+	
+	
 }
src/main/java/itn/let/kakao/kakaoComm/KakaoVO.java
--- src/main/java/itn/let/kakao/kakaoComm/KakaoVO.java
+++ src/main/java/itn/let/kakao/kakaoComm/KakaoVO.java
@@ -344,6 +344,7 @@
 		sb.append("\n , subMsgSendYn=[").append(subMsgSendYn).append("]");
 		sb.append("\n , subMsgTxtReplYn=[").append(subMsgTxtReplYn).append("]");
 		sb.append("\n , subMsgType=[").append(subMsgType).append("]");
+		sb.append("\n , subMsgTxt=[").append(subMsgTxt).append("]");
 		sb.append("\n , reserveYn=[").append(getReserveYn()).append("]");
 		sb.append("\n , menuTopTab=[").append(menuTopTab).append("]");
 		sb.append("\n , bizJsonYn=[").append(bizJsonYn).append("]");
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
@@ -10,6 +10,7 @@
 import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.lang3.StringUtils;
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -246,12 +247,12 @@
 		JSONObject templateImageExtInfo = new JSONObject();
 		String imageType = kakaoVO.getImageType();
 		
-		if(!imageType.equals("")) {
+		if(StringUtils.isNotEmpty(imageType)) {
 			templateImageInfo.put("img_url", kakaoVO.getTemplateImageUrl());
 			templateImageInfo.put("img_link", kakaoVO.getImgLink());
 		}
 		
-		if(imageType.equals("W")) {
+		if("W".equals(imageType)) {
 			templateImageExtInfo.put("wide", "Y");
 		}
 		
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
@@ -913,7 +913,7 @@
 		
 		
 /** @전송금액 확인 --------------------------------------------------*/
-		if (!isCashSufficient(userId, kakaoSendAdvcListVO)) {
+		if (!kakaoSendUtil.isCashSufficient(userId, kakaoSendAdvcListVO)) {
 			log.error("Insufficient balance for message sending.");
 			return new StatusResponse(HttpStatus.BAD_REQUEST, "문자 발송에 필요한 보유 잔액이 부족 합니다.");
 		}
@@ -921,7 +921,8 @@
 		
 
 /** @json파일이 있을 떄 biz_attachments insert */
-		this.insertKakaoAtDataJsonInfo_advc(kakaoSendAdvcListVO);
+		kakaoSendUtil.insertKakaoAtDataJsonInfo_advc(kakaoSendAdvcListVO);
+//		this.insertKakaoAtDataJsonInfo_advc(kakaoSendAdvcListVO);
 		
 		
 		Map<String, List<KakaoSendAdvcVO>> priceGroupedMessages = kakaoSendAdvcListVO.stream()
@@ -941,7 +942,7 @@
 			
 
 			// 발송 데이터 삽입
-			int instCnt = this.insertKakaoData_advc(groupedMsgList);
+			int instCnt = kakaoSendUtil.insertKakaoData_advc(groupedMsgList);
 //			int instCnt = 6;
 			
 			if(instCnt > 0) {
@@ -951,7 +952,7 @@
 				KakaoSendAdvcVO sendVO = groupedMsgList.get(0);
 
 /** @groupData 테이블 insert */
-				this.insertKakaoGroupDataTb_advc(instCnt, kakaoVO, sendVO);
+				kakaoSendUtil.insertKakaoGroupDataTb_advc(instCnt, kakaoVO, sendVO);
 
 
 /** @biz_kakao_price에 insert (대체문자 환불관련 테이블)*/ 
@@ -1032,146 +1033,26 @@
 	
 
 
-	private void insertKakaoAtDataJsonInfo_advc(List<KakaoSendAdvcVO> kakaoSendAdvcListVO) {
-		// TODO Auto-generated method stub
-
-		// 측정할 메소드 호출 전 시간 기록
-		List<KakaoSendAdvcVO> jsonInfoData = new ArrayList<>(kakaoSendAdvcListVO);
-		jsonInfoData.removeIf(t -> StringUtils.isBlank(t.getJsonStr()));
-		log.info(" + jsonInfoData Insert :: [{}]", jsonInfoData.size());
-		if(jsonInfoData.size() > 0) {
-			kakaoAlimTalkDAO.insertKakaoAtDataJsonInfo_advc(jsonInfoData);
-		}
-
-	}
-
-	private void insertKakaoGroupDataTb_advc(int instCnt, KakaoVO kakaoVO, KakaoSendAdvcVO sendVO) throws Exception {
-		// TODO Auto-generated method stub
-
-//		log.info(" + insertKakaoGroupDataTb_advc kakaoVO :: \n[{}]", kakaoVO.toString());;
-//		log.info(" + insertKakaoGroupDataTb_advc kakaoSendAdvcVOList :: \n[{}]", sendVO.toString());
-		
-		sendVO.setMsgGroupCnt(Integer.toString(instCnt));
-		sendVO.setReserveYn(kakaoVO.getReserveYn());
-		sendVO.setBefCash(priceAndPoint.getBefCash(sendVO.getUserId()));
-		sendVO.setBefPoint(priceAndPoint.getBefPoint(sendVO.getUserId()));
-		
-		Float eachPrice = Float.parseFloat(sendVO.getEachPrice());
-
-		Float totPrice = eachPrice * instCnt;
-		sendVO.setTotPrice(String.format("%.1f", totPrice));
-		
-		sendVO.setAtDelayYn(kakaoVO.getAtSmishingYn()); 
-		sendVO.setBizKakaoResendOrgnlTxt(kakaoVO.getSubMsgTxt());
-		sendVO.setBizKakaoResendType(sendVO.getSubMsgType());
-		
-		kakaoAlimTalkDAO.insertKakaoGroupDataTb_advc(sendVO);
-		
-	}
-
-	/**
-	 * @methodName	: insertKakaoData_advc 
-	 * @author		: 이호영
-	 * @date		: 2025. 3. 20.
-	 * @description	: 카카오 batch 발송 => mj_msg_data
-	 * @return : int
-	 * @param kakaoSendAdvcVOList
-	 * @param parentLoopCount
-	 * @param isJsonNotEmpty
-	 * @param isJsonNameAllSame
-	 * @return
-	 * 
-	 */
-	private int insertKakaoData_advc(List<KakaoSendAdvcVO> kakaoSendAdvcVOList) {
-
-
-		// 시작 시간 측정
-		long totalStartTime = System.currentTimeMillis();
-
-		int totalSize = kakaoSendAdvcVOList.size(); // 총 데이터 개수
-		// Batch 크기 설정 (고정값)
-//		int batchSize = 10000; 465
-		int batchSize = 50000; // 9분 18초
-
-		log.info("총 데이터 개수 :: [{}] ", totalSize);
-		log.info("설정된 Batch 크기 :: [{}] ", batchSize);
-
-		// 총 insert 카운트
-		int instCnt = 0;
-		int batchCount = 0;
-
-		// 각 배치별 실행 시간 기록
-		List<Double> batchExecutionTimes = new ArrayList<>();
-
-	
-		// 첫 번째 배치에서만 삽입했는지 추적하는 플래그
-		for (int i = 0; i < totalSize; i += batchSize) {
-			// Batch 시작 시간 측정
-			long batchStartTime = System.currentTimeMillis();
-
-			// Batch 리스트 생성
-			List<KakaoSendAdvcVO> batchList = kakaoSendAdvcVOList.subList(i, Math.min(i + batchSize, totalSize));
-			System.out.println("Batch 시작 인덱스: " + i);
-
-			// mj_msg_data 테이블 insert
-			int insertedCount = kakaoAlimTalkDAO.insertKakaoAtDataInfo_advc(batchList);
-			
-			/** @kakaoSendUtil.populateSendLists  
-			 * 하단에서 
-			 * getJsonStr 데이터 처리 후 활용
-			 *  */
-			instCnt += insertedCount;
-
-			// Batch 종료 시간 측정 및 실행 시간 계산
-			long batchEndTime = System.currentTimeMillis();
-			double batchExecutionTimeInSeconds = (batchEndTime - batchStartTime) / 1000.0;
-
-			// 실행 시간 기록
-			batchExecutionTimes.add(batchExecutionTimeInSeconds);
-			batchCount++;
-		}
-
-		// 종료 시간 측정
-		long totalEndTime = System.currentTimeMillis();
-
-		// 총 실행 시간 계산 (밀리초 -> 초로 변환)
-		double totalExecutionTimeInSeconds = (totalEndTime - totalStartTime) / 1000.0;
-
-		// 실행 시간 출력
-		log.info("총 배치 실행 횟수 :: [{}] ", batchCount);
-		log.info("batchSize :: [{}] ", batchSize);
-		log.info("총 실행 시간 :: [{}] ", totalExecutionTimeInSeconds + "초");
-		log.info("총 삽입 건수 :: [{}] ", instCnt);
-
-		// 각 배치별 실행 시간 출력
-		for (int k = 0; k < batchExecutionTimes.size(); k++) {
-			System.out.println("배치 " + (k + 1) + " 실행 시간 :: " + batchExecutionTimes.get(k) + "초");
-		}
-
-		return instCnt;
-		
-	}
-
-	// 보유 금액이 충분한지 확인하는 메서드
-	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.getEachPrice()))) // 변환 오류 방지
-			.reduce(BigDecimal.ZERO, BigDecimal::add)
-			.setScale(2, RoundingMode.HALF_EVEN); // 일관성 유지
-
-		// 비교 수행
-		return befCash.compareTo(totalEachPrice) >= 0;
-	}
+//	// 보유 금액이 충분한지 확인하는 메서드
+//	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.getEachPrice()))) // 변환 오류 방지
+//			.reduce(BigDecimal.ZERO, BigDecimal::add)
+//			.setScale(2, RoundingMode.HALF_EVEN); // 일관성 유지
+//
+//		// 비교 수행
+//		return befCash.compareTo(totalEachPrice) >= 0;
+//	}
 	
 	
 	
src/main/java/itn/let/kakao/user/kakaoFt/service/impl/KakaoFriendsTalkServiceImpl.java
--- src/main/java/itn/let/kakao/user/kakaoFt/service/impl/KakaoFriendsTalkServiceImpl.java
+++ src/main/java/itn/let/kakao/user/kakaoFt/service/impl/KakaoFriendsTalkServiceImpl.java
@@ -1,9 +1,12 @@
 package itn.let.kakao.user.kakaoFt.service.impl;
 
+import java.time.Duration;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
@@ -20,9 +23,13 @@
 import itn.let.kakao.kakaoComm.KakaoSendAdvcVO;
 import itn.let.kakao.kakaoComm.KakaoSendUtil;
 import itn.let.kakao.kakaoComm.KakaoVO;
+import itn.let.kakao.user.kakaoAt.service.impl.KakaoAlimTalkDAO;
 import itn.let.kakao.user.kakaoFt.service.KakaoFriendsTalkService;
 import itn.let.mail.service.StatusResponse;
 import itn.let.mjo.mjocommon.MjonCommon;
+import itn.let.mjo.msg.service.MjonMsgVO;
+import itn.let.mjo.msg.service.impl.MjonMsgDAO;
+import itn.let.module.base.PriceAndPoint;
 import itn.let.uss.umt.service.EgovUserManageService;
 import lombok.extern.slf4j.Slf4j;
 
@@ -36,16 +43,28 @@
 	@Resource(name="kakaoFriendsTalkTemplateDAO")
 	private KakaoFriendsTalkTemplateDAO kakaoFriendsTalkTemplateDAO;
 
+	@Resource(name="mjonMsgDAO")
+	private MjonMsgDAO mjonMsgDAO;
+	
 	/** userManageService */
 	@Resource(name = "userManageService")
 	private EgovUserManageService userManageService;
 
-	@Autowired
-	private MjonCommon mjonCommon;
+	@Resource(name = "egovMjonMsgGroupIdGnrService")
+	private EgovIdGnrService idgenMjonMsgGroupId;
 
+	
+	@Resource(name="kakaoAlimTalkDAO")
+	private KakaoAlimTalkDAO kakaoAlimTalkDAO;
 	
 	@Autowired
 	KakaoSendUtil kakaoSendUtil;
+	
+	@Autowired
+	private MjonCommon mjonCommon;
+	
+	@Autowired
+	private PriceAndPoint priceAndPoint;
 	
 	@Override
 	public StatusResponse insertKakaoFtSandAjax_advc(KakaoVO kakaoVO, HttpServletRequest request) throws Exception {
@@ -103,29 +122,122 @@
 		
 		
 		
+/** @전송금액 확인 --------------------------------------------------*/
+		if (!kakaoSendUtil.isCashSufficient(userId, kakaoSendAdvcListVO)) {
+			log.error("Insufficient balance for message sending.");
+			return new StatusResponse(HttpStatus.BAD_REQUEST, "문자 발송에 필요한 보유 잔액이 부족 합니다.");
+		}
 		
-		
-		
-		
-		
-		
-		
-		
-		
-		
-		
-		
-		
-		
-		
-		
-		
-		// KakaoSendAdvcVO 발송 VO
 
+/** @json파일이 있을 떄 biz_attachments insert */
+		kakaoSendUtil.insertKakaoAtDataJsonInfo_advc(kakaoSendAdvcListVO);
 		
 		
+
+		Map<String, List<KakaoSendAdvcVO>> priceGroupedMessages = kakaoSendAdvcListVO.stream()
+				.collect(Collectors.groupingBy(KakaoSendAdvcVO::getEachPrice));
+		// instTotalCnt : 화면에서 보여줄 총 발송건수
+		int instTotalCnt = 0;
+		
+
+		// 임시
+		List<String> nextMsgGroupIdA = new ArrayList<>();
+			// 대안: entrySet() 직접 사용
+		for (Map.Entry<String, List<KakaoSendAdvcVO>> entry : priceGroupedMessages.entrySet()) {
+			    // entry 사용
+
+			List<KakaoSendAdvcVO> groupedMsgList = entry.getValue(); // 해당 가격의 메시지 리스트
+
+			String nextMsgGroupId = idgenMjonMsgGroupId.getNextStringId();
+			groupedMsgList.forEach(t -> t.setMsgGroupId(nextMsgGroupId));
+			
+
+			// 발송 데이터 삽입
+			int instCnt = kakaoSendUtil.insertKakaoData_advc(groupedMsgList);
+//			int instCnt = 6;
+			
+			if(instCnt > 0) {
+
+				instTotalCnt += instCnt;
+
+				KakaoSendAdvcVO sendVO = groupedMsgList.get(0);
+
+/** @groupData 테이블 insert */
+				kakaoSendUtil.insertKakaoGroupDataTb_advc(instCnt, kakaoVO, sendVO);
+
+
+/** @biz_kakao_price에 insert (대체문자 환불관련 테이블)*/ 
+				kakaoVO.setMsgGroupId(sendVO.getMsgGroupId());
+				kakaoVO.setKakaoAtPrice(Float.parseFloat(sendVO.getEachPrice()));
+				kakaoVO.setSmsPrice(Float.parseFloat(sendVO.getSmsPrice()));
+				kakaoVO.setMmsPrice(Float.parseFloat(sendVO.getMmsPrice()));
+				
+				kakaoAlimTalkDAO.insertKakaoSendPrice(kakaoVO);
+				
+				
+				priceAndPoint.insertCashAndPoint(kakaoVO.getUserId()
+						, -Float.parseFloat(sendVO.getTotPrice())
+						, "카카오 알림톡 총 "+groupedMsgList.size()+"건 중 " + instCnt + "건 발송"
+						, nextMsgGroupId
+					);
+
+
+/** @SLACK발송 */ 
+				/** @발송조건이되면 발송 */ 
+				if(isNotified) {
+					mjonCommon.getAdminKakaoAtSendSlack(sendVO);
+				}else if("Y".equals(kakaoVO.getAtSmishingYn())){
+				/** @발송조건이 안되면 DB INSERT */ 
+					mjonMsgDAO.insertSpamPassMsgData(MjonMsgVO.builder()
+							.msgGroupId(nextMsgGroupId)
+							.userId(kakaoVO.getUserId())
+							.reqDate(kakaoVO.getReqDate())
+							.smsTxt(groupedMsgList.get(0).getTemplateContent())
+							.totalCallCnt(instCnt)
+							.callFrom(kakaoVO.getCallFrom())
+							.msgType("8")
+							.reserveYn(kakaoVO.getReserveYn())
+							.build()
+						);
+				}
+				
+				nextMsgGroupIdA.add(nextMsgGroupId);
+				
+			}
+			
+		}
+
+		returnMap.put("resultSts", instTotalCnt);
+		returnMap.put("reserYn", kakaoVO.getReserveYn());
+		returnMap.put("groupIds", nextMsgGroupIdA);
+		
+		
+		// 측정할 메소드 호출 후 시간 기록
+		Instant end = Instant.now();
+
+		log.info(" + start :: [{}]", start);
+		// 실행 시간 계산 (나노초, 밀리초, 초)
+		long seconds = Duration.between(start, end).getSeconds();
+		log.info("메소드 실행 시간 (초): {} s", seconds);
+		double minutes = seconds / 60.0; // 소수점 포함을 위해 60.0으로 나눔
+
+		returnMap.put("second", seconds+" s");
+		returnMap.put("minutes", minutes+" min");
+		
+
+//		System.out.println("메소드 실행 시간 (분): " + minutes + " min");
+		
+		
+		
+		
+//		priceAndPoint.getBefCash(userId);
+		
+		
+		
+
 		statusResponse.setStatus(HttpStatus.OK);
-//		statusResponse.setObject(returnMap);
+		statusResponse.setObject(returnMap);
+		
 		return statusResponse;
 	}
 	
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
@@ -700,7 +700,7 @@
 		success: function (data) {
 				console.log('data : ', data);
 				
-				var status = data.status;
+			/* 	var status = data.status;
 				if("OK" == status){
 					var resultSts = data.object.resultSts;
 					var reserYn = data.object.reserYn;
@@ -715,7 +715,7 @@
 				}else{
 					alert(data.message);
 					return false;
-				}
+				} */
 				
 				
 // 				if(data == 'success'){
src/main/webapp/WEB-INF/jsp/web/kakao/msgdata/ft/KakaoFriendsTalkMsgDataView.jsp
--- src/main/webapp/WEB-INF/jsp/web/kakao/msgdata/ft/KakaoFriendsTalkMsgDataView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/kakao/msgdata/ft/KakaoFriendsTalkMsgDataView.jsp
@@ -313,17 +313,12 @@
 		alert("이미지 클릭시 이동할 URL 주소를 http:// 또는 https:// 포함하여 입력해 주세요.");
 		return false;
 		
-	}else{
-		
-		if(link.search("http://") == -1 && link.search("https://") == -1){
-			
-			$("#imgNm").text("");
-			$("#imgFile").val("");
-			alert("이미지 URL 주소에는 http:// 또는 https://를 포함하여 입력해야 합니다.");
-			return false;
-			
-		}
-		
+	}else if(link.search("http://") == -1 && link.search("https://") == -1){
+
+		$("#imgNm").text("");
+		$("#imgFile").val("");
+		alert("이미지 URL 주소에는 http:// 또는 https://를 포함하여 입력해야 합니다.");
+		return false;
 	}
 	
 	//첨부파일 선택 팝업 호출해주기
@@ -891,8 +886,10 @@
 	var subMsgSendYn = "N";
 	if($("#send_fail_check").is(":checked")){
 		subMsgSendYn = 'Y'
+		$('#callFrom').val($('#callFromList').val()) 
 	}
 	$("#subMsgSendYn").val(subMsgSendYn);
+	$("#subMsgTxt").val( $('#smsTxtArea').val());
 		
 	
 	
@@ -912,166 +909,120 @@
 		
 	}
 	
-	var spamChk = true;
+		
+
+	// 타블레이터 호출
+	var $selectedData = tableL.getData(); // 데이터 가져오기
 	
-	var spmData = new FormData(document.bizForm);
-	$.ajax({
-		type: "POST"
-		, url: "/web/mjon/kakao/friendstalk/selectSpamKakaoFriendsTalkMsgChkAjax.do"
-		, data: spmData
-		, dataType:'json'
-		, async: false
-		, processData: false
-		, contentType: false
-		, cache: false
-		, success: function (returnData, status) {
-			if(status == 'success'){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
-				
-				if("fail" == returnData.result){
-					alert(returnData.message);
-					spamChk = false;
-					return false;
-				}else if("loginFail" == returnData.result){
-					alert(returnData.message);
-					spamChk = false;
-					return false;
-				}else if("spams" == returnData.result){
-					//alert("전송 내용에  스팸문구가 포함되어 있습니다.")
-					$("#spamStatus").val("Y");
-					return false;
-				}else{
-					spamChk = true;
-					return false;
-				}
-				
-			} else if(status== 'fail'){
-				alert(returnData.message);
-				return false;
-			}
-		}
-		, error: function (e) {
-			alert("문자 발송에 실패하였습니다.");
-			console.log("ERROR : ", e);
-			return false;
+	var data = $('#bizForm');
+	var formDataArray = data.serializeArray();
+	
+	// 배열을 객체로 변환
+	var formData = {};
+	$.each(formDataArray, function(index, field) {
+		formData[field.name] = field.value;
+	});
+
+	// 2. buttonVOList 수동으로 수집
+	var buttonList = [];
+	$('input[name^="buttonVOList"]').each(function() {
+	    let nameAttr = $(this).attr('name');
+	    let match = nameAttr.match(/buttonVOList\[(\d+)\]\.(\w+)/);
+
+	    if (match) {
+	        let index = parseInt(match[1]);
+	        let key = match[2];
+	        let value = $(this).val();
+
+	        if (!buttonList[index]) buttonList[index] = {};
+	        buttonList[index][key] = value;
+	    }
+	});
+
+	// 3. formData에 배열로 추가
+	formData["buttonVOList"] = buttonList;
+
+	// 4. 기존의 buttonVOList[0].xxx 형태 제거
+	Object.keys(formData).forEach(function(key) {
+		if (/^buttonVOList\[\d+\]\./.test(key)) {
+		    delete formData[key];
 		}
 	});
 	
-	if(spamChk){
-		
+	// VO에 정의되어있지 않는 필요없는 값은 제거
+	["adFlag", "img_file_add", "userMoney", "callToList"].forEach(function(key) {
+		  delete formData[key];
+	});
 
-		// 타블레이터 호출
-		var $selectedData = tableL.getData(); // 데이터 가져오기
-		
-		var data = $('#bizForm');
-		var formDataArray = data.serializeArray();
-		
-		// 배열을 객체로 변환
-		var formData = {};
-		$.each(formDataArray, function(index, field) {
-			formData[field.name] = field.value;
-		});
-
-		// 2. buttonVOList 수동으로 수집
-		var buttonList = [];
-		$('input[name^="buttonVOList"]').each(function() {
-		    let nameAttr = $(this).attr('name');
-		    let match = nameAttr.match(/buttonVOList\[(\d+)\]\.(\w+)/);
-
-		    if (match) {
-		        let index = parseInt(match[1]);
-		        let key = match[2];
-		        let value = $(this).val();
-
-		        if (!buttonList[index]) buttonList[index] = {};
-		        buttonList[index][key] = value;
-		    }
-		});
-
-		// 3. formData에 배열로 추가
-		formData["buttonVOList"] = buttonList;
-
-		// 4. 기존의 buttonVOList[0].xxx 형태 제거
-		Object.keys(formData).forEach(function(key) {
-			if (/^buttonVOList\[\d+\]\./.test(key)) {
-			    delete formData[key];
-			}
-		});
-		
-		// VO에 정의되어있지 않는 필요없는 값은 제거
-		["adFlag", "img_file_add", "userMoney", "callToList"].forEach(function(key) {
-			  delete formData[key];
-		});
-
-		
-		// 빈 값 제거
-		removeEmptyValues(formData);
-		// 선택된 데이터 추가
-		formData["mjonFTSendVOList"] = $selectedData;
-		// JSON 데이터 확인
-		console.log("최종 formData:", JSON.stringify(formData));
-		
-		
-		
-		
-		$.ajax({
-			type: "POST"
-			, url: "/web/mjon/kakao/friendstalk/kakaoFriendsTalkMsgSendAjax_advc.do"
-			, data: JSON.stringify(formData)
-			, contentType: 'application/json'
-			, dataType: 'json'
-			, success: function (returnData) {
-				
-				console.log('returnData : ', returnData);
-				
-				
-				/* 
-				if(status == 'success'){
-					if("loginFail" == returnData.result){
-						
-						alert(returnData.message);
-						return false;
-						
-					}else  if('fail' == returnData.result){
-						
-						alert(returnData.message);
-						return false;
-						
-					}else if('authFail' == returnData.result){
-						
-						alert(returnData.message);
-						location.reload();
-						
-					} else if(status == 'success'){
-						
-						var kakaoSendCnt = returnData.resultSts;
-						
-						$('.pop_msg_success').css({'display':'block','opacity':'1','left':'50%','top':'50%','transform':'translate(-50%,-50%)'});
-						
-						//예약발송 건의 경우 결과 팝업 문구 변경
-						if(reserYn == 'Y'){
-							$('.pop_msg_success .msg_text').html("예약 성공 : <strong>"+ kakaoSendCnt + "</strong>건의<br>친구톡이 예약 되었습니다.");
-						}else{
-							$('.pop_msg_success .msg_text').html("발송 성공 : <strong>"+ kakaoSendCnt + "</strong>건의<br>친구톡이 발송 되었습니다.");
-						}
-						
-						$('.mask').addClass('on');
+	
+	// 빈 값 제거
+	removeEmptyValues(formData);
+	// 선택된 데이터 추가
+	formData["mjonFTSendVOList"] = $selectedData;
+	// JSON 데이터 확인
+	console.log("최종 formData:", JSON.stringify(formData));
+	
+	
+	
+	
+	$.ajax({
+		type: "POST"
+		, url: "/web/mjon/kakao/friendstalk/kakaoFriendsTalkMsgSendAjax_advc.do"
+		, data: JSON.stringify(formData)
+		, contentType: 'application/json'
+		, dataType: 'json'
+		, success: function (returnData) {
+			
+			console.log('returnData : ', returnData);
+			
+			
+			/* 
+			if(status == 'success'){
+				if("loginFail" == returnData.result){
+					
+					alert(returnData.message);
+					return false;
+					
+				}else  if('fail' == returnData.result){
+					
+					alert(returnData.message);
+					return false;
+					
+				}else if('authFail' == returnData.result){
+					
+					alert(returnData.message);
+					location.reload();
+					
+				} else if(status == 'success'){
+					
+					var kakaoSendCnt = returnData.resultSts;
+					
+					$('.pop_msg_success').css({'display':'block','opacity':'1','left':'50%','top':'50%','transform':'translate(-50%,-50%)'});
+					
+					//예약발송 건의 경우 결과 팝업 문구 변경
+					if(reserYn == 'Y'){
+						$('.pop_msg_success .msg_text').html("예약 성공 : <strong>"+ kakaoSendCnt + "</strong>건의<br>친구톡이 예약 되었습니다.");
+					}else{
+						$('.pop_msg_success .msg_text').html("발송 성공 : <strong>"+ kakaoSendCnt + "</strong>건의<br>친구톡이 발송 되었습니다.");
 					}
-				} */
-			}
-			,beforeSend : function(xmlHttpRequest) {
-				//로딩창 show
-				$('.loading_layer').addClass('active');
-			}
-			,complete : function(xhr, textStatus) {
-				//로딩창 hide
-				$('.loading_layer').removeClass('active');
-			}
-			,error: function (e) {
-				console.log("ERROR : ", e);
-				alert("카카오 친구톡 전송에 실패하였습니다.");
-			}
-		}); 
-	}
+					
+					$('.mask').addClass('on');
+				}
+			} */
+		}
+		,beforeSend : function(xmlHttpRequest) {
+			//로딩창 show
+			$('.loading_layer').addClass('active');
+		}
+		,complete : function(xhr, textStatus) {
+			//로딩창 hide
+			$('.loading_layer').removeClass('active');
+		}
+		,error: function (e) {
+			console.log("ERROR : ", e);
+			alert("카카오 친구톡 전송에 실패하였습니다.");
+		}
+	}); 
 	
 }
 
@@ -1117,6 +1068,8 @@
 function fn_insertErrorYN(val){
 	$('#errorChk').val(val);
 }
+
+
 
 
 //문자 바이트수 계산하기 함수
@@ -1282,154 +1235,6 @@
 		
 		alert("받는사람 주소를 한 건 이상 입력해주세요.");
 		return false;
-	
-	}else{
-		
-		//치환문구 변환 
-		var txtReplYn = $("#txtReplYn").val();
-		
-		if(txtReplYn == 'Y'){
-			
-			var name = tableL.getRows()[0].getData().name;
-			var phone = removeDash(tableL.getRows()[0].getData().phone);
-			var rep1 = tableL.getRows()[0].getData().rep1;
-			var rep2 = tableL.getRows()[0].getData().rep2;
-			var rep3 = tableL.getRows()[0].getData().rep3;
-			var rep4 = tableL.getRows()[0].getData().rep4;
-			
-			var varValList = [];		//치환문자 연결시킬 변수 셋팅
-			
-			
-			var nmStatus = false;
-			var rep1Status = false;
-			var rep2Status = false;
-			var rep3Status = false;
-			var rep4Status = false;
-			
-			var varValStr = "";
-			var varValStatus = true;
-
-			
-			if(tmpContents.indexOf("\#{이름}")  > -1){
-				nmStatus = true;
-			}
-			
-			if(tmpContents.indexOf("\#{1}")  > -1){
-				rep1Status = true;
-			}
-			
-			if(tmpContents.indexOf("\#{2}")  > -1){
-				rep2Status = true;
-			}
-			
-			if(tmpContents.indexOf("\#{3}")  > -1){
-				rep3Status = true;
-			}
-			
-			if(tmpContents.indexOf("\#{4}")  > -1){
-				rep4Status = true;
-			}
-			
-	
-			if(nmStatus && (typeof(name) != 'undefined' && name != null && name !="")){
-				
-				if(varValStr == ''){
-					varValStr = name.replaceAll(",","§");
-				}else{
-					varValStr = varValStr + "¶" +  name.replaceAll(",","§");
-				}
-				
-			}else{
-				
-				if(nmStatus){
-					varValStatus = false;
-				}
-				
-			}
-			
-			if(varValStr == ''){
-				varValStr = phone;
-			}else{
-				varValStr = varValStr + "¶" +  phone;
-			}
-
-			if(rep1Status && (typeof(rep1) != 'undefined' && rep1 != null && rep1 !="")){
-				
-				if(varValStr == ''){
-					varValStr = rep1.replaceAll(",","§");
-				}else{
-					varValStr = varValStr + "¶" +  rep1.replaceAll(",","§");
-				}
-				
-			}else{
-				
-				if(rep1Status){
-					varValStatus = false;
-				}
-				
-			}
-			
-			
-			if(rep2Status && (typeof(rep2) != 'undefined' && rep2 != null && rep2 !="")){
-			
-				if(varValStr == ''){
-					varValStr = rep2.replaceAll(",","§");
-				}else{
-					varValStr = varValStr + "¶" +  rep2.replaceAll(",","§");
-				}
-				
-			}else{
-				
-				if(rep2Status){
-					varValStatus = false;
-				}
-				
-			}
-			
-			if(rep3Status && (typeof(rep3) != 'undefined' && rep3 != null && rep3 !="")){
-				
-				if(varValStr == ''){
-					varValStr = rep3.replaceAll(",","§");
-				}else{
-					varValStr = varValStr + "¶" +  rep3.replaceAll(",","§");
-				}
-				
-			}else{
-				
-				if(rep3Status){
-					varValStatus = false;
-				}
-				
-			}
-			
-			if(rep4Status && (typeof(rep4) != 'undefined' && rep4 != null && rep4 !="")){
-				
-				if(varValStr == ''){
-					varValStr = rep4.replaceAll(",","§");
-				}else{
-					varValStr = varValStr + "¶" +  rep4.replaceAll(",","§");
-				}
-			
-			}else{
-				
-				if(rep4Status){
-					varValStatus = false;
-				}
-				
-			}
-			
-			if(!varValStatus){
-				
-				alert("특정문구 일괄변환에 대한 일부 데이터가 누락된 부분이 있습니다. 데이터를 확인해 주세요.");
-				return false;
-				
-			}
-			
-			varValList[0] = varValStr;
-			
-			$("#varValList").val(varValList);
-		}
-		
 	}
 
 	form.method = "post"; 
@@ -1730,13 +1535,13 @@
 															<p>주소록, 엑셀에 입력된 내용을 이용해 수신자마다 다른 내용의 메시지를 발송하는 기능</p>
 														</div>
 														<div class="convers_middle">
-															<a href="javascript:void(0)" class="changeWord" value="\#{이름}"><c:out value="\#{이름}"/></a>
+															<a href="javascript:void(0)" class="changeWord" value="[*이름*]"><c:out value="[*이름*]"/></a>
 														</div>
 														<div class="convers_bottom">
-															<a href="javascript:void(0)" class="changeWord" value="\#{1}"><c:out value="\#{1}"/></a>
-															<a href="javascript:void(0)" class="changeWord" value="\#{2}"><c:out value="\#{2}"/></a>
-															<a href="javascript:void(0)" class="changeWord" value="\#{3}"><c:out value="\#{3}"/></a>
-															<a href="javascript:void(0)" class="changeWord" value="\#{4}"><c:out value="\#{4}"/></a>
+															<a href="javascript:void(0)" class="changeWord" value="[*1*]"><c:out value="[*1*]"/></a>
+															<a href="javascript:void(0)" class="changeWord" value="[*2*]"><c:out value="[*2*]"/></a>
+															<a href="javascript:void(0)" class="changeWord" value="[*3*]"><c:out value="[*3*]"/></a>
+															<a href="javascript:void(0)" class="changeWord" value="[*4*]"><c:out value="[*4*]"/></a>
 														</div>
 													</div>
 													<button type="button" class="btn_close" onclick="miniPopup(this)">닫기</button>
@@ -2053,7 +1858,7 @@
 																	<ul class="thumb_wrap liOnImg ui-sortable"></ul>
 																	<!-- //업로드한 이미지의 썸네일 영역 -->
 																	<label for="smsTxtArea" class="label"></label>
-																	<textarea id="smsTxtArea" name="smsTxtArea" class="put_text"></textarea>
+																	<textarea id="smsTxtArea" class="put_text"></textarea>
 																	<div class="text_length">
 																		<div name="afterDeny">
 																			<p>
@@ -2066,7 +1871,7 @@
 																</div>
 																<div class="put_right">
 																	<button type="button" class="btnType btnType9" id="failCheckInit">초기화</button>
-																	<button type="button" class="btnType btnType7" onclick="javascript:fn_errorChk(); return false;">오류검사<i class="qmMark"></i></button>
+<!-- 																	<button type="button" class="btnType btnType7" onclick="javascript:fn_errorChk(); return false;">오류검사<i class="qmMark"></i></button> -->
 																</div>
 															</div>
 														</td>
src/main/webapp/WEB-INF/jsp/web/kakao/sent/KakaoSentView.jsp
--- src/main/webapp/WEB-INF/jsp/web/kakao/sent/KakaoSentView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/kakao/sent/KakaoSentView.jsp
@@ -557,10 +557,10 @@
 					<ul class="list_tab">
 						<li class="tab active"><button type="button" onclick="fnTabLoad('',0); return false;">전체</button></li>
 						<li class="tab"><button type="button" onclick="fnTabLoad('at', 1); return false;">알림톡</button></li>
-						<%-- <c:if test="${pageContext.request.serverName == 'localhost' 
+						<c:if test="${pageContext.request.serverName == 'localhost' 
 						            || pageContext.request.serverName == '119.193.215.98'}">
 							<li class="tab"><button type="button" onclick="fnTabLoad('ft', 2); return false;">친구톡</button></li>
-				        </c:if> --%>
+				        </c:if>
 					</ul><!--// tab button -->
 				</div>
 				<!-- 예약관리 > 전체 -->
Add a comment
List