이호영 이호영 2024-09-27
문자발송로직 impl 화 완료 -> 리펙토링 진행 중
@3c6e83d10bb9d0f12545f775a9ad00fddca32403
 
src/main/java/itn/com/cmm/util/MsgSendUtils.java (added)
+++ src/main/java/itn/com/cmm/util/MsgSendUtils.java
@@ -0,0 +1,468 @@
+package itn.com.cmm.util;
+
+import java.io.UnsupportedEncodingException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+
+import itn.let.mail.service.StatusResponse;
+import itn.let.mjo.msg.service.MjonMsgVO;
+import itn.let.mjo.msgdata.service.ReplacementListsVO;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 
+ * @author 		: 이호영
+ * @fileName 	: MsgSendUtils.java 
+ * @date 		: 2024.09.23
+ * @description : 메세지발송 데이터 다루는 유틸
+ * =========================================================== 
+ * DATE          AUTHOR   NOTE 
+ * ----------------------------------------------------------- *
+ * 2024.09.23    이호영          최초 생성
+ * 
+ * 
+ * 
+ */
+@Slf4j
+public final class MsgSendUtils {
+
+	
+	/** 
+	 * @methodName	: getSmsTxtBytes 
+	 * @author		: 이호영
+	 * @date		: 2024.09.23 
+	 * @description	: sms 텍스트 바이트 계산 후 return;
+	 * @param smsTxt
+	 * @return
+	 * @throws UnsupportedEncodingException 
+	 */
+	public static int getSmsTxtBytes(String smsTxt) throws UnsupportedEncodingException {        	//문자열 길이 체크 해주기
+		int smsBytes = 0;
+		//문자 바이트 계산에 필요한 캐릭터 셋 : 한글 2Byte로 계산
+		String charset = "euc-kr"; 						
+		if(StringUtils.isNotEmpty(smsTxt)) {
+			String smsCont = smsTxt.replace("\r\n", "\n");
+			smsBytes = smsCont.getBytes(charset).length;
+		}
+		return smsBytes;
+	}
+
+	/** 
+	 * @methodName	: getMsgType 
+	 * @author		: 이호영
+	 * @date		: 2024.09.23 
+	 * @description	: msgType 재정의
+	 * @param mjonMsgVO
+	 * @param smsTxtByte
+	 * @return 
+	 */
+	public static String getMsgType(MjonMsgVO mjonMsgVO, int smsTxtByte) {
+	    String msgType = mjonMsgVO.getMsgType();
+
+	    // 내문자저장함에 저장 후 문자를 발송하는 경우 문자 타입이 숫자가 아닌 문자로 넘어와서 변경 처리함
+	    if ("P".equals(msgType) || "L".equals(msgType)) {
+	        msgType = "6";
+	    } else if ("S".equals(msgType)) {
+	        msgType = "4";
+	    }
+
+	    // 그림 이미지가 첨부된 경우 장문으로 설정
+	    if (mjonMsgVO.getFileName1() != null || (mjonMsgVO.getImgFilePath() != null && mjonMsgVO.getImgFilePath().length > 0)) {
+	        msgType = "6";
+	    } else if (smsTxtByte > 2000) {
+	        // 2000 Byte를 초과할 경우 에러 처리 (이 부분은 호출부에서 검사하도록 유지할 수도 있음)
+	        return "INVALID";  // 이 값은 호출부에서 에러 처리를 하도록 활용할 수 있습니다.
+	    } else if (smsTxtByte > 90) {
+	        // 90Byte 초과 시 장문으로 설정
+	        msgType = "6";
+	    } else {
+	        // 그 외 단문으로 설정
+	        msgType = "4";
+	    }
+
+	    return msgType;
+	}
+
+	public static float getValidPrice(Float personalPrice, Float defaultPrice) {
+	    return (personalPrice != null && personalPrice > 0) ? personalPrice : defaultPrice;
+	}
+
+	
+	/** 
+	* @methodName	: determinePriceByMsgType 
+	* @author		: 이호영
+	* @date		: 2024.09.24 
+	* @description	: 문자 메시지 타입에 따라 단가를 결정하는 메서드
+	* @param mjonMsgVO
+	* @param shortPrice
+	* @param longPrice
+	* @param picturePrice
+	* @param picture2Price
+	* @param picture3Price
+	* @return 
+	*/
+	public static float determinePriceByMsgType(MjonMsgVO mjonMsgVO, float shortPrice, float longPrice, 
+	                                      float picturePrice, float picture2Price, float picture3Price) {
+	    float price = 0;
+	    String msgType = mjonMsgVO.getMsgType();
+
+	    if ("4".equals(msgType)) {
+	        price = shortPrice;
+	    } else if ("6".equals(msgType)) {
+	        // 파일 첨부 여부에 따라 장문 단가 설정
+	        if (mjonMsgVO.getFileName3() != null) {
+	            price = picture3Price;
+	        } else if (mjonMsgVO.getFileName2() != null) {
+	            price = picture2Price;
+	        } else if (mjonMsgVO.getFileName1() != null) {
+	            price = picturePrice;
+	        } else {
+	            price = longPrice;
+	        }
+	    }
+	    return price;
+	}
+
+	public static boolean isReplacementRequired(MjonMsgVO mjonMsgVO) {
+	    return "Y".equals(mjonMsgVO.getTxtReplYn());
+	}
+
+	public static boolean isReplacementDataValid(MjonMsgVO mjonMsgVO) {
+		String[] nameList = mjonMsgVO.getNameList();
+		String[] rep1 = mjonMsgVO.getRep1List();
+		String[] rep2 = mjonMsgVO.getRep2List();
+		String[] rep3 = mjonMsgVO.getRep3List();
+		String[] rep4 = mjonMsgVO.getRep4List();
+
+
+		
+		// 치환 문자 리스트가 유효한지 확인
+		return !(isEmpty(nameList) 
+				&& isEmpty(rep1) 
+				&& isEmpty(rep2) 
+				&& isEmpty(rep3) 
+				&& isEmpty(rep4));
+	}
+
+	// 배열이 비어있는지 확인하는 메서드
+	public static boolean isEmpty(String[] array) {
+		return array == null || array.length == 0;
+	}
+
+	public static ReplacementListsVO createReplacementLists(MjonMsgVO mjonMsgVO) throws UnsupportedEncodingException {
+		ReplacementListsVO lists = new ReplacementListsVO();
+		lists.initializeLists(mjonMsgVO); // 배열을 초기화합니다.
+		return lists;
+//		return populateReplacementLists(mjonMsgVO, lists); // 데이터를 배열에 채웁니다.
+	}
+
+	/** 
+	 * @methodName	: populateReplacementLists 
+	 * @author		: 이호영
+	 * @date		: 2024.09.26 
+	 * @description	: 배열에 데이터를 채우는 메서드 
+	 * @param mjonMsgVO
+	 * @param lists
+	 * @param statusResponse
+	 * @return
+	 * @throws UnsupportedEncodingException 
+	 */
+	public static void populateReplacementLists(MjonMsgVO mjonMsgVO, ReplacementListsVO lists, StatusResponse statusResponse){
+		
+		
+	    String[] nameList = mjonMsgVO.getNameList();
+	    String[] phone = mjonMsgVO.getCallToList();
+	    String[] rep1 = mjonMsgVO.getRep1List();
+	    String[] rep2 = mjonMsgVO.getRep2List();
+	    String[] rep3 = mjonMsgVO.getRep3List();
+	    String[] rep4 = mjonMsgVO.getRep4List();
+	    String smsTxt = mjonMsgVO.getSmsTxt();
+	    int fileCount = Integer.parseInt(mjonMsgVO.getFileCnt());
+
+        // 이름 치환
+		int shortCnt = 0;
+		int longCnt = 0;
+		int imgCnt = 0;
+	    for (int i = 0; i < phone.length; i++) {
+	    	
+	        smsTxt = smsTxt.replaceAll(String.valueOf((char) 13), "");
+	        if (smsTxt.contains("[*이름*]")) {
+	            if (nameList.length > i && StringUtil.isNotEmpty(nameList[i])) {
+	                smsTxt = smsTxt.replaceAll("\\[\\*이름\\*\\]", StringUtil.getString(nameList[i].replaceAll("§", ",")));
+	            } else {
+	                smsTxt = smsTxt.replaceAll("\\[\\*이름\\*\\]", "");
+	            }
+	        }
+
+	        // 문자 1 치환
+	        if (smsTxt.contains("[*1*]")) {
+	            if (rep1.length > i && StringUtil.isNotEmpty(rep1[i])) {
+	                smsTxt = smsTxt.replaceAll("\\[\\*1\\*\\]", StringUtil.getString(rep1[i].replaceAll("§", ",")));
+	            } else {
+	                smsTxt = smsTxt.replaceAll("\\[\\*1\\*\\]", "");
+	            }
+	        }
+
+	        // 문자 2 치환
+	        if (smsTxt.contains("[*2*]")) {
+	            if (rep2.length > i && StringUtil.isNotEmpty(rep2[i])) {
+	                smsTxt = smsTxt.replaceAll("\\[\\*2\\*\\]", StringUtil.getString(rep2[i].replaceAll("§", ",")));
+	            } else {
+	                smsTxt = smsTxt.replaceAll("\\[\\*2\\*\\]", "");
+	            }
+	        }
+
+	        // 문자 3 치환
+	        if (smsTxt.contains("[*3*]")) {
+	            if (rep3.length > i && StringUtil.isNotEmpty(rep3[i])) {
+	                smsTxt = smsTxt.replaceAll("\\[\\*3\\*\\]", StringUtil.getString(rep3[i].replaceAll("§", ",")));
+	            } else {
+	                smsTxt = smsTxt.replaceAll("\\[\\*3\\*\\]", "");
+	            }
+	        } 
+
+	        // 문자 4 치환
+	        if (smsTxt.contains("[*4*]")) {
+	            if (rep4.length > i && StringUtil.isNotEmpty(rep4[i])) {
+	                smsTxt = smsTxt.replaceAll("\\[\\*4\\*\\]", StringUtil.getString(rep4[i].replaceAll("§", ",")));
+	            } else {
+	                smsTxt = smsTxt.replaceAll("\\[\\*4\\*\\]", "");
+	            }
+	        }
+
+			try {
+				int bytes = getSmsTxtBytes(smsTxt);
+	        
+				if(bytes < 2000) {
+					if(fileCount > 0) {
+					    populateImgLists(lists, nameList, phone, rep1, rep2, rep3, rep4, smsTxt, i);
+					    imgCnt++;
+					    
+					}else if(bytes > 90) {//장문문자 리스트 만들기
+					    populateLongLists(lists, nameList, phone, rep1, rep2, rep3, rep4, smsTxt, i);
+					    longCnt++;
+	    			} else {//단문문자 리스트 만들기
+	    			    populateShortLists(lists, nameList, phone, rep1, rep2, rep3, rep4, smsTxt, i);
+	    			    shortCnt++;
+	    			}
+				}else {
+					statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다.");
+				}
+				
+			} catch (UnsupportedEncodingException e) {
+				statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "전송 중 오류가 발생하였습니다.");
+				e.printStackTrace();
+			}
+	    }
+	    
+		lists.setShortCnt(shortCnt);
+		lists.setLongCnt(longCnt);
+		lists.setImgCnt(imgCnt);
+
+	}
+
+	private static void populateShortLists(ReplacementListsVO lists, String[] nameList, String[] phone, String[] rep1,
+			String[] rep2, String[] rep3, String[] rep4, String smsTxt, int i) {
+        // 이름 치환
+        if (smsTxt.contains("[*이름*]")) {
+            if (nameList.length > i && StringUtil.isNotEmpty(nameList[i])) {
+                lists.getShortNameList()[i] = StringUtil.getString(nameList[i].replaceAll("§", ","));
+            } else {
+                lists.getShortNameList()[i] = " ";
+            }
+        } else {
+            lists.getShortNameList()[i] = getSafeValue(nameList, i);
+        }
+
+        lists.getShortPhone()[i] = getSafeValue(phone, i);
+
+        // 문자 1 치환
+        if (smsTxt.contains("[*1*]")) {
+            lists.getShortRep1()[i] = getReplacementValue(rep1, i, "[*1*]");
+        } else {
+            lists.getShortRep1()[i] = getSafeValue(rep1, i);
+        }
+
+        // 문자 2 치환
+        if (smsTxt.contains("[*2*]")) {
+            lists.getShortRep2()[i] = getReplacementValue(rep2, i, "[*2*]");
+        } else {
+            lists.getShortRep2()[i] = getSafeValue(rep2, i);
+        }
+
+        // 문자 3 치환
+        if (smsTxt.contains("[*3*]")) {
+            lists.getShortRep3()[i] = getReplacementValue(rep3, i, "[*3*]");
+        } else {
+            lists.getShortRep3()[i] = getSafeValue(rep3, i);
+        }
+
+        // 문자 4 치환
+        if (smsTxt.contains("[*4*]")) {
+            lists.getShortRep4()[i] = getReplacementValue(rep4, i, "[*4*]");
+        } else {
+            lists.getShortRep4()[i] = getSafeValue(rep4, i);
+        }
+	}
+
+	// 장문 리스트에 데이터를 채우는 메서드
+	private static void populateLongLists(ReplacementListsVO lists, String[] nameList, String[] phone, String[] rep1, String[] rep2, String[] rep3, String[] rep4, String smsTxt, int i) {
+        // 이름 치환
+        if (smsTxt.contains("[*이름*]")) {
+            if (nameList.length > i && StringUtil.isNotEmpty(nameList[i])) {
+                lists.getLongNameList()[i] = StringUtil.getString(nameList[i].replaceAll("§", ","));
+            } else {
+                lists.getLongNameList()[i] = " ";
+            }
+        } else {
+            lists.getLongNameList()[i] = getSafeValue(nameList, i);
+        }
+
+        lists.getLongPhone()[i] = getSafeValue(phone, i);
+
+        // 문자 1 치환
+        if (smsTxt.contains("[*1*]")) {
+            lists.getLongRep1()[i] = getReplacementValue(rep1, i, "[*1*]");
+        } else {
+            lists.getLongRep1()[i] = getSafeValue(rep1, i);
+        }
+
+        // 문자 2 치환
+        if (smsTxt.contains("[*2*]")) {
+            lists.getLongRep2()[i] = getReplacementValue(rep2, i, "[*2*]");
+        } else {
+            lists.getLongRep2()[i] = getSafeValue(rep2, i);
+        }
+
+        // 문자 3 치환
+        if (smsTxt.contains("[*3*]")) {
+            lists.getLongRep3()[i] = getReplacementValue(rep3, i, "[*3*]");
+        } else {
+            lists.getLongRep3()[i] = getSafeValue(rep3, i);
+        }
+
+        // 문자 4 치환
+        if (smsTxt.contains("[*4*]")) {
+            lists.getLongRep4()[i] = getReplacementValue(rep4, i, "[*4*]");
+        } else {
+            lists.getLongRep4()[i] = getSafeValue(rep4, i);
+        }
+	}
+
+	// 그림 문자 리스트에 데이터를 채우는 메서드
+	private static void populateImgLists(ReplacementListsVO lists, String[] nameList, String[] phone, String[] rep1, String[] rep2, String[] rep3, String[] rep4, String smsTxt, int i) {
+		
+		// 이름 치환
+		if (smsTxt.contains("[*이름*]")) {
+		    if (nameList.length > i && StringUtil.isNotEmpty(nameList[i])) {
+		        lists.getImgNameList()[i] = StringUtil.getString(nameList[i].replaceAll("§", ","));
+		    } else {
+		        lists.getImgNameList()[i] = " ";
+		    }
+		} else {
+		    lists.getImgNameList()[i] = getSafeValue(nameList, i);
+		}
+		
+		lists.getImgPhone()[i] = getSafeValue(phone, i);
+		
+		// 문자 1 치환
+		if (smsTxt.contains("[*1*]")) {
+		    lists.getImgRep1()[i] = getReplacementValue(rep1, i, "[*1*]");
+		} else {
+		    lists.getImgRep1()[i] = getSafeValue(rep1, i);
+		}
+		
+		// 문자 2 치환
+		if (smsTxt.contains("[*2*]")) {
+		    lists.getImgRep2()[i] = getReplacementValue(rep2, i, "[*2*]");
+		} else {
+		    lists.getImgRep2()[i] = getSafeValue(rep2, i);
+		}
+		
+		// 문자 3 치환
+		if (smsTxt.contains("[*3*]")) {
+		    lists.getImgRep3()[i] = getReplacementValue(rep3, i, "[*3*]");
+		} else {
+		    lists.getImgRep3()[i] = getSafeValue(rep3, i);
+		}
+		
+		// 문자 4 치환
+		if (smsTxt.contains("[*4*]")) {
+		    lists.getImgRep4()[i] = getReplacementValue(rep4, i, "[*4*]");
+		} else {
+		    lists.getImgRep4()[i] = getSafeValue(rep4, i);
+		}
+		
+	}
+
+	// 배열 인덱스를 안전하게 접근하는 메서드
+	private static String getSafeValue(String[] array, int index) {
+	    return (array != null && array.length > index) ? array[index] : " ";
+	}
+
+	// 치환 처리에 특수문자 제거 로직이 포함된 메서드
+	private static String getReplacementValue(String[] array, int index, String placeholder) {
+	    if (array != null && array.length > index && StringUtil.isNotEmpty(array[index])) {
+	        return StringUtil.getString(array[index].replaceAll("§", ","));
+	    } else {
+	        return " ";
+	    }
+	}
+	
+
+	/** 
+	 * @methodName	: checkReplacementDataValidity 
+	 * @author		: 이호영
+	 * @date		: 2024.09.25
+	 * @description	: 치환문자가 사용될 때 데이터가 올바른지 확인하는 메서드
+	 * @param mjonMsgVO
+	 * @param modelAndView
+	 * @return boolean
+	 */
+	public static boolean checkReplacementDataValidity(MjonMsgVO mjonMsgVO, StatusResponse statusResponse) {
+		String[] nameList = mjonMsgVO.getNameList();
+		String[] phone = mjonMsgVO.getCallToList();
+		String[] rep1 = mjonMsgVO.getRep1List();
+		String[] rep2 = mjonMsgVO.getRep2List();
+		String[] rep3 = mjonMsgVO.getRep3List();
+		String[] rep4 = mjonMsgVO.getRep4List();
+
+		boolean isRepCountOk = true;
+
+		// 치환 문자 전체 필수 체크
+		if (mjonMsgVO.getSmsTxt().contains("[*이름*]") && nameList.length != phone.length) {
+			isRepCountOk = false;
+		}
+		if (mjonMsgVO.getSmsTxt().contains("[*1*]") && rep1.length != phone.length) {
+			isRepCountOk = false;
+		}
+		if (mjonMsgVO.getSmsTxt().contains("[*2*]") && rep2.length != phone.length) {
+			isRepCountOk = false;
+		}
+		if (mjonMsgVO.getSmsTxt().contains("[*3*]") && rep3.length != phone.length) {
+			isRepCountOk = false;
+		}
+		if (mjonMsgVO.getSmsTxt().contains("[*4*]") && rep4.length != phone.length) {
+			isRepCountOk = false;
+		}
+
+		// 검증 결과가 false인 경우 에러 메시지 반환
+		if (!isRepCountOk) {
+
+			statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "특정문구 일괄변환 치환문자 데이터가 없습니다.");
+			return false;
+		}
+
+		return true;
+	}
+	
+
+	private static void statusResponseSet (StatusResponse statusResponse, HttpStatus httpStatus, String msg ) {
+		statusResponse.setStatus(httpStatus);
+		statusResponse.setMessage(msg);
+		
+	}
+
+	
+}
src/main/java/itn/let/mail/service/StatusResponse.java
--- src/main/java/itn/let/mail/service/StatusResponse.java
+++ src/main/java/itn/let/mail/service/StatusResponse.java
@@ -85,13 +85,19 @@
 		this.timestamp = timestamp;
 		this.messageTemp = messageTemp;
 	}
-	
-	@Builder
-	public StatusResponse(HttpStatus status, String msg, Object data) {
+
+	public StatusResponse(HttpStatus status, String message) {
 		this.status = status;
-		this.message = msg;
-		this.object = data;
+		this.message = message;
 	}
+
+
+	public StatusResponse(HttpStatus status, String message, Object object) {
+		this.status = status;
+		this.message = message;
+		this.object = object;
+	}
+	
  
 	
 	
src/main/java/itn/let/mjo/msgdata/service/MjonMsgDataService.java
--- src/main/java/itn/let/mjo/msgdata/service/MjonMsgDataService.java
+++ src/main/java/itn/let/mjo/msgdata/service/MjonMsgDataService.java
@@ -2,7 +2,10 @@
 
 import java.util.List;
 
+import javax.servlet.http.HttpServletRequest;
+
 import itn.let.lett.service.LetterVO;
+import itn.let.mail.service.StatusResponse;
 import itn.let.mjo.addr.service.AddrVO;
 import itn.let.mjo.msg.service.MjonMsgVO;
 import itn.let.sym.site.service.JoinSettingVO;
@@ -179,6 +182,8 @@
 	
 	//팩스 거래명세서 합산 정보
 	public List<MjonMsgVO> selectPayUserSumFaxList(MjonMsgVO mjonMsgVO) throws Exception;
+
+	public StatusResponse sendMsgData_advc(MjonMsgVO mjonMsgVO, HttpServletRequest request) throws Exception;
 	
 	
 }
 
src/main/java/itn/let/mjo/msgdata/service/ReplacementListsVO.java (added)
+++ src/main/java/itn/let/mjo/msgdata/service/ReplacementListsVO.java
@@ -0,0 +1,59 @@
+package itn.let.mjo.msgdata.service;
+
+import itn.let.mjo.msg.service.MjonMsgVO;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+public class ReplacementListsVO {
+	String[] shortNameList;
+	String[] shortPhone;
+	String[] shortRep1;
+	String[] shortRep2;
+	String[] shortRep3;
+	String[] shortRep4;
+	int shortCnt;
+
+	String[] longNameList;
+	String[] longPhone;
+	String[] longRep1;
+	String[] longRep2;
+	String[] longRep3;
+	String[] longRep4;
+	int longCnt;
+
+	String[] imgNameList;
+	String[] imgPhone;
+	String[] imgRep1;
+	String[] imgRep2;
+	String[] imgRep3;
+	String[] imgRep4;
+	int imgCnt;
+
+	// 배열을 초기화하는 메서드
+	public void initializeLists(MjonMsgVO mjonMsgVO) {
+		shortNameList = new String[Integer.parseInt(mjonMsgVO.getShortMsgCnt())];
+		shortPhone = new String[shortNameList.length];
+		shortRep1 = new String[shortNameList.length];
+		shortRep2 = new String[shortNameList.length];
+		shortRep3 = new String[shortNameList.length];
+		shortRep4 = new String[shortNameList.length];
+
+		longNameList = new String[Integer.parseInt(mjonMsgVO.getLongMsgCnt())];
+		longPhone = new String[longNameList.length];
+		longRep1 = new String[longNameList.length];
+		longRep2 = new String[longNameList.length];
+		longRep3 = new String[longNameList.length];
+		longRep4 = new String[longNameList.length];
+
+		imgNameList = new String[mjonMsgVO.getCallToList().length];
+		imgPhone = new String[imgNameList.length];
+		imgRep1 = new String[imgNameList.length];
+		imgRep2 = new String[imgNameList.length];
+		imgRep3 = new String[imgNameList.length];
+		imgRep4 = new String[imgNameList.length];
+	}
+}
src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataServiceImpl.java
--- src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataServiceImpl.java
+++ src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataServiceImpl.java
This diff is too big to display.
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
@@ -1795,7 +1795,7 @@
 		 * @return
 		 * @throws Exception
 		 */
-		@RequestMapping(value= {"/web/mjon/msgdata/selectMsgAddrListAjax_advc.do"})
+		@RequestMapping(value= {"/web/mjon/msgdata/selectMsgAddrListAjaxQ.do"})
 		public ResponseEntity<StatusResponse> selectMsgAddrListAjax_advc(@ModelAttribute("searchVO") AddrVO addrVO) {
 			
 		    Map<String, Object> response = new HashMap<>();
@@ -2197,7 +2197,7 @@
         		 * 현재 로그인 세션도 만료 처리함
         		 * */
         		boolean mberSttus = userManageService.selectUserStatusInfo(userId);
-        		
+        			
         		if(!mberSttus) {
         			
         			modelAndView.addObject("message", "현재 고객님께서는 문자온 서비스 이용이 정지된 상태로 문자를 발송하실 수 없습니다. 이용정지 해제를 원하시면 고객센터로 연락주시기 바랍니다.");
@@ -2247,14 +2247,10 @@
         	
         	//메세지 타입이 단문이면 진짜 단문인지 한번더 확인해 준다.
         	if(msgType.equals("4")) {
-        		
         		//메세지 길이가 90Byte를초과 하거나, 그림 이미지가 있는경우 메세지 타입을 6으로 변경해준다.
         		if(FrBytes > 90 || mjonMsgVO.getImgFilePath().length > 0) {
-        			
         			msgType = "6";
-        			
         		}
-        		
         	}
         	
         	mjonMsgVO.setMsgType(msgType);
@@ -3155,6 +3151,25 @@
  		return modelAndView;
 	}
 	
+
+    /**
+     * 문자 발송  기능
+     * @param searchVO
+     * @param model
+     * @return	"/web/mjon/msgdata/sendMsgDataAjax.do"
+     * @throws Exception
+     */
+	@RequestMapping(value= {"/web/mjon/msgdata/sendMsgDataAjax_advc.do"})
+	public ResponseEntity<StatusResponse> sendMsgData_advc(@ModelAttribute("searchVO") MjonMsgVO mjonMsgVO, 
+			RedirectAttributes redirectAttributes, 
+			HttpServletRequest request,
+			ModelMap model) throws Exception{
+		
+		return ResponseEntity.ok().body(mjonMsgDataService.sendMsgData_advc(mjonMsgVO, request)) ;
+		
+	}
+	
+	
 	/**
 	 * 관리자로 문자 발송해주기
 	 * 사용자가 보낸 문자를 문자온 법인폰으로 발송해주는 기능 함수.
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
@@ -250,6 +250,7 @@
 //전체 데이터 갯수 구하는 함수
 function updateTotCnt(data){
 	
+	console.log(' :: updateTotCnt :: ');
 	var rowTotCnt = data;
 	$("#rowTotCnt").text(numberWithCommas(rowTotCnt));
 
@@ -746,8 +747,8 @@
 	
 	//문자 내용 입력시 바이트수 계산하기
 	$('#smsTxtArea').keyup(function(e){
-		console.log("11$('.preview_auto').test() :: ",$('.realtime').html())
-		console.log("11$('.preview_auto').test() :: ",$('.realtime').text())
+// 		console.log("11$('.preview_auto').test() :: ",$('.realtime').html())
+// 		console.log("11$('.preview_auto').test() :: ",$('.realtime').text())
 		
 		var contents = $(this).val();
 		var adrYn = $("input[name=send_adYn]:checked").val();
@@ -2200,7 +2201,7 @@
 	}
 	
 	//수신목록 전체 데이터 갯수 구하기
-	updateTotCnt(totRows);
+// 	updateTotCnt(totRows);
 	
 	//일괄변환 문구 결제금액 처리
 	if(contents.indexOf("[*이름*]")  > -1
src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataView.jsp
--- src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataView.jsp
@@ -1050,20 +1050,25 @@
 		alert("현재 문자 발송하기 기능 점검 중입니다.\n\n1분 후 다시 시도해주세요.");
 		return false;
 	}
-	
-	var testString = document.msgForm.smsTxtArea.value;
 
 	var form = document.msgForm;
-	
-	//회원 보유 잔액 비교
 	var totPriceOnly = stringReplaceAll(form.totPrice.value, ",", "");
 	var userMoneyOnly = stringReplaceAll(form.myPrice.value, ",", "");
+
 	if(parseFloat(userMoneyOnly) < parseFloat(totPriceOnly)){
 		alert("문자 발송에 필요한 회원님의 보유 잔액이 부족 합니다.");
 		return false;
 	}
-	
+
 	var loginVO = '${LoginVO}';
+	if (!loginVO) {
+		alert("문자발송 서비스는 로그인 후 이용 가능합니다.");
+		return false;
+	}
+
+	// 폼 유효성 검사
+	if (!validateForm(form)) return false;
+	
 	var adverYn = $("input[name='send_adYn']:checked").val();
 	var spamStatus = false;
 	var exceptSpamYn = $("#exceptSpamYn").val(); //금지어 필터링 예외 여부 - N 일 경우만 스팸 검사를 진행
@@ -1104,69 +1109,6 @@
 	//	return false;	 	
 	//}
 	
-	if(form.callFromList.value == ""){
-		
-		alert("발신번호를 입력해 주세요.");
-		return false;
-		
-	}
-	
-	var titleStatus = form.title_status.value;
-	if(titleStatus == 'N'){//장문 제목 사용안함으로 선택시 제목에 있는 데이터 지워주기
-		
-		form.mmsSubject.value = "";
-		
-	}else{//장문 제목에 치환문자 포함된 경우 입력 못하도록 처리.
-		
-		var mmsSubject = form.mmsSubject.value;
-		if(getSpacialStringChk(mmsSubject)){
-			alert("문자 제목에는 치환문자(엑셀 내 *이름*, *1*, *2*, *3*, *4* 등)를 사용하실 수 없습니다.");
-			return false;
-		}
-	
-	}
-	
-	//문자내용 첫글자에 특수기호 포함 여부 체크
-	var strCont = form.smsTxtArea.value;
-	var rtnStr = strChinJpnCheck(strCont);
-	
-	//문자제목에 이모지가 있는지 체크
-	var titleStatusYn = $("input[name='title_status']:checked").val();
-	if(titleStatusYn == 'Y') {
-		if(!emojiCheck(form.mmsSubject.value)) return false;
-	}
-	
-	//문자내용에 이모지가 있는지 체크 
-	if(!emojiCheck(strCont)) return false;
-	
-	if(rtnStr.length > 0){
-		
-		alert("입력하신 문구 중 \" " + rtnStr + " \" 는 일부 휴대폰에서 표기되지 않을 수 있습니다.");
-		
-	}
-	
-	/* var strCont = form.smsTxtArea.value;
-	var repStr = strFirstCharCheck(strCont);
-	
-	if(repStr.length > 0){
-		
-		alert("문자 내용 첫 글자는 특수기호가 들어갈 수 없습니다.");
-		$('#smsTxtArea').val(strCont.replace(repStr, ""));
-		fnByteString(strCont.replace(repStr, ""));
-		return false;
-		
-	} */
-	
-	if(imgFilePath.length == 0){ // 그림문자일 경우 내용이 없어도 됨 , 장문 문자일 경우만 문자내용 체크함
-		
-		if(form.smsTxtArea.value == ""){
-			
-			alert("문자 내용을 입력해 주세요.");
-			return false;
-			
-		}
-		
-	}
 	
 	
 	//광고 문자 내용 합쳐주기
@@ -1312,7 +1254,8 @@
 		
 	}
 
-	
+
+	console.log(' : 전송하시겠습니까 : ')
 	if(confirm("문자를 전송하시겠습니까?")){
 		imgFilePath = [];
 		$('.thumb_wrap').find('.thumb_img').each(function(idx, el) {
@@ -1390,7 +1333,7 @@
 		
 		//문자내용이 입력된 경우 스팸 필터링 실행
 		if(!form.smsTxtArea.value == "" && exceptSpamYn == "N"){
-			
+			console.log(' : selectSpamTxtChkAjax : ')
 			var spmData = new FormData(form);
 			url = "/web/mjon/msgdata/selectSpamTxtChkAjax.do";
 			
@@ -1452,6 +1395,7 @@
 		}
 		
 		var eventRemainCash = parseFloat(form.eventRemainCash.value);
+		console.log('eventStatus : ', eventStatus);
 		
 		if(eventStatus == 'Y'){
 			
@@ -1629,8 +1573,9 @@
 						var form = document.msgForm;
 						
 						var data = new FormData(form);
-						url = "/web/mjon/msgdata/sendMsgDataAjax.do";
-						
+// 						url = "/web/mjon/msgdata/sendMsgDataAjax.do";
+						url = "/web/mjon/msgdata/sendMsgDataAjax_advc.do";
+						console.log('url :: ', url);
 						$.ajax({
 					        type: "POST",
 					        url: url,
@@ -1641,6 +1586,7 @@
 					        contentType: false,
 					        cache: false,
 					        success: function (returnData, status) {
+					        	console.log('returnData : ', returnData);
 								if(status == 'success'){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
 									if("fail" == returnData.result){
 										
@@ -1794,7 +1740,7 @@
 							form.eventStatus.value = 'N';
 							form.eventYn.value = 'N';
 							
-							sendMsgAjax(0,0);
+							sendMsgAjax_advc(0,0);
 							
 						}else{
 							
@@ -1815,14 +1761,14 @@
 			}else{
 				
 				//발송 Ajax 호출해주기
-				sendMsgAjax(0,0);
+				sendMsgAjax_advc(0,0);
 				
 			}
 			
 		}else{
 			
 			//발송 Ajax 호출해주기
-			sendMsgAjax(0,0);
+			sendMsgAjax_advc(0,0);
 			
 		}		
 		
@@ -1830,6 +1776,46 @@
 	
 }
 
+
+
+//폼 유효성 검사 함수
+function validateForm(form) {
+	
+	if(form.callFromList.value == ""){
+		
+		alert("발신번호를 입력해 주세요.");
+		return false;
+		
+	}
+
+	if (form.title_status.value === 'N') {
+		form.mmsSubject.value = "";
+	} else if (getSpacialStringChk(form.mmsSubject.value)) {
+		alert("문자 제목에는 치환문자(엑셀 내 *이름*, *1*, *2*, *3*, *4* 등)를 사용하실 수 없습니다.");
+		return false;
+	}
+
+	//문자제목에 이모지가 있는지 체크
+	var titleStatusYn = $("input[name='title_status']:checked").val();
+	if(titleStatusYn == 'Y') {
+		if(!emojiCheck(form.mmsSubject.value)) return false;
+	}
+
+	// 문자내용에 이모지가 있는지 체크 
+	var strCont = form.smsTxtArea.value;
+	if (!emojiCheck(strCont)) return false;
+	
+	var rtnStr = strChinJpnCheck(strCont);
+	if(rtnStr.length > 0){
+		alert("입력하신 문구 중 \" " + rtnStr + " \" 는 일부 휴대폰에서 표기되지 않을 수 있습니다.");
+	}
+	
+	if (imgFilePath.length === 0 && !form.smsTxtArea.value) {
+		alert("문자 내용을 입력해 주세요.");
+		return false;
+	}
+	return true;
+}
 
 //이벤트가 아닌 일반 개별 단가 셋팅해주기
 function getNorEachPrice(evnMsgType){
@@ -1873,13 +1859,103 @@
 }
 
 
-function sendMsgAjax(paramSmsCnt, paramBlockCnt){
-	
+function sendMsgAjax_advc(paramSmsCnt, paramBlockCnt){
+	console.log('sendMsgAjax : ');
 	var form = document.msgForm;
 	var reserYn = $("#reserveYn").val();
 	
 	var data = new FormData(form);
-	url = "/web/mjon/msgdata/sendMsgDataAjax.do";
+// 	url = "/web/mjon/msgdata/sendMsgDataAjax.do";
+	url = "/web/mjon/msgdata/sendMsgDataAjax_advc.do";
+	
+	$.ajax({
+        type: "POST",
+        url: url,
+        data: data,
+        dataType:'json',
+        async: true,
+        processData: false,
+        contentType: false,
+        cache: false,
+        success: function (data) {
+        	console.log('data : ', data);
+        	/*  message:"특정문구 일괄변환 치환문자 데이터가 없습니다."
+				status:"BAD_REQUEST" */
+        	
+			var status = data.status;
+// 			if(status == 'success'){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
+			if("OK" == status){
+				
+				var smsCnt = Number(data.object.resultSts);
+				var blockCnt = Number(data.object.resultBlockSts);
+				
+				smsCnt = Number(smsCnt) + Number(paramSmsCnt);
+				blockCnt = Number(blockCnt) + Number(paramBlockCnt);
+				
+				if((smsCnt + blockCnt) == 0){
+					
+					$('.pop_msg_spam').css({'display':'block','opacity':'1','left':'50%','top':'50%','transform':'translate(-50%,-50%)'});
+					$('.pop_msg_spam .msg_text').html("문자 발송(예약)에 실패하였습니다.<br/> 다시 시도해주세요. <br/>* 정상적으로 발송 시도하였으나 실패하신 경우 혹시 문자내용에 사용불가 이모지 <br/>또는 복사-붙여넣기로 인한 보이지 않는 특수문자가 포함되었는지 확인 후 다시 시도해주세요.");
+					$('.mask').addClass('on');
+					
+				}else{
+					
+					$('.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>"+ smsCnt + "</strong>건,수신거부 : <span>" + blockCnt + "</span>건의<br>문자가 예약 되었습니다.");
+					}else{
+						$('.pop_msg_success .msg_text').html("발송 성공 : <strong>"+ smsCnt + "</strong>건,수신거부 : <span>" + blockCnt + "</span>건의<br>문자가 발송 되었습니다.");
+					}
+					
+					$('.mask').addClass('on');
+					
+				}
+				
+				
+			}else if("BAD_REQUEST" == status){
+
+				alert(data.message);
+				return false;
+					
+			}else if("UNAUTHORIZED" == status){
+				
+				alert(data.message);
+				//문자발송 URL Move
+				goMsgUrlMove();
+				return false;
+				
+			}else if("NO_CONTENT" == status){
+				
+				$('.pop_msg_fails').css({'display':'block','opacity':'1','left':'50%','top':'50%','transform':'translate(-50%,-50%)'});
+				$('.pop_msg_fails .msg_text').html(returnData.message);
+				$('.mask').addClass('on');
+				
+			}
+				
+// 			} else if(status== 'fail'){
+// 				alert(returnData.message);
+// 			}
+		},
+		beforeSend : function(xmlHttpRequest) {
+        	//로딩창 show
+        	$('.loading_layer').addClass('active');				
+		},	        	        
+        complete : function(xhr, textStatus) {
+        	//로딩창 hide
+        	$('.loading_layer').removeClass('active');
+		},	      			
+        error: function (e) { alert("문자 발송에 실패하였습니다."); console.log("ERROR : ", e); }
+    });
+	
+}
+
+function sendMsgAjax(paramSmsCnt, paramBlockCnt){
+	var form = document.msgForm;
+	var reserYn = $("#reserveYn").val();
+	
+	var data = new FormData(form);
+ 	url = "/web/mjon/msgdata/sendMsgDataAjax.do";
 	
 	$.ajax({
         type: "POST",
src/main/webapp/WEB-INF/jsp/web/msgdata/excel/MsgExcelDataView.jsp
--- src/main/webapp/WEB-INF/jsp/web/msgdata/excel/MsgExcelDataView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/msgdata/excel/MsgExcelDataView.jsp
@@ -1082,7 +1082,10 @@
 						var form = document.msgForm;
 						
 						var data = new FormData(form);
-						url = "/web/mjon/msgdata/sendMsgDataAjax.do";
+// 						url = "/web/mjon/msgdata/sendMsgDataAjax.do";
+						url = "/web/mjon/msgdata/sendMsgDataAjax_advc.do";
+
+						console.log('url : ', url);
 						
 						$.ajax({
 					        type: "POST",
@@ -1094,6 +1097,8 @@
 					        contentType: false,
 					        cache: false,
 					        success: function (returnData, status) {
+					        	console.log('returnData : ', returnData);
+					        	console.log('status : ', status);
 								if(status == 'success'){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
 									if("fail" == returnData.result){
 										
src/main/webapp/WEB-INF/jsp/web/msgdata/include/msgDataIncludeExcel.jsp
--- src/main/webapp/WEB-INF/jsp/web/msgdata/include/msgDataIncludeExcel.jsp
+++ src/main/webapp/WEB-INF/jsp/web/msgdata/include/msgDataIncludeExcel.jsp
@@ -1,938 +1,940 @@
-<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
-<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-
-<script type="text/javascript" src="<c:url value='/publish/js/content.js'/>"></script>
-
-<script type="text/javascript">
-
-var $tableExcel = null; //엑셀입력 탭
-var $tableError = null; //엑셀입력 탭
-$(document).ready(function(){
-
-	//Tabulator AJAX Data Loading
-	$tableError = new Tabulator("#tabulator_error", {
-		height:"255px",
-		width:"100%",
-	    layout:"fitColumns",
-	    autoColumns:false,
-	    headerHozAlign:"center", 
-	    validationMode:"highlight",
-	    clipboard:false,
-	    clipboardCopySelector:"table",
-	    clipboardPasteAction:"insert", // insert, update, replace
-	    placeholder:"등록 팝업에서 휴대폰을 선택 후 확인해주세요.", //fit columns to width of table (optional)
-	 	columns:[ //Define Table Columns
-		 	{title:"이름", field:"name", hozAlign:"center", headerHozAlign: "center", width:125},
-		 	{title:"휴대폰", field:"phone", hozAlign:"center", headerHozAlign: "center", width:158},
-		 	{title:"미등록 결과", field:"result", hozAlign:"center", headerHozAlign: "center", width:125}
-	 	]
-	});
-	
-
-	//Tabulator AJAX Data Loading
-	$tableExcel = new Tabulator("#tabulator_excel", {
-		height:"255px",
-		width:"100%",
-	    layout:"fitColumns",
-	    autoColumns:false,
-	    headerHozAlign:"center", 
-	    validationMode:"highlight",
-	    clipboard:false,
-	    clipboardCopySelector:"table",
-	    clipboardPasteAction:"insert", // insert, update, replace
-	    placeholder:"Excel 파일을 업로드 해주세요.", //fit columns to width of table (optional)
-	 	columns:[ //Define Table Columns
-	 		{formatter:"rowSelection", titleFormatter:"rowSelection",clipboard:false, headerHozAlign:"center", hozAlign:"center", headerSort:false, cellClick:function(e, cell){
-	 	        cell.getRow().toggleSelect();
-		 		}
-		 	}, 
-			{formatter:"rownum", align:"center" ,title:"No", hozAlign:"center", headerHozAlign:"center", width:40},
-		 	{title:"A", field:"A", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
-		 	{title:"B", field:"B", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
-		 	{title:"C", field:"C", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
-		 	{title:"D", field:"D", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
-		 	{title:"E", field:"E", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
-		 	{title:"F", field:"F", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]}
-	 	],
-	 	validationFailed:function(cell, value, parameters){ // 유효성 체크 함수 
-	        var valid = cell.isValid();
-	 		if(!valid){
-	 			alert("양식에 맞지 않는 정보가 입력되었습니다.");
-	 			
-	 			//해당 셀 데이터 삭제
-	 			cell.setValue("");
-	 		}
-	 		return value % parameters.phone;
-	    },
-	});
-	
-	
-    
-	
-	// 타뷸레이터 width값 변경 시 위에 select width 값 변경
-	var titleArray = ["A","B","C","D","E","F"];
-
-	$tableExcel.on("columnWidth",function(column){
-		var titleIndex = titleArray.indexOf(column._column.definition.title);
-		titleIndex += 1;
-		if(titleIndex != 0){
-			$('.select_adr_hd>div').eq(titleIndex).css('width', column._column.width);
-		}else{
-			$('.select_adr_hd>div').eq(0).css('width', column._column.width + 40);
-		}
-	});
-
-	$tableExcel.on("scrollHorizontal",function(left){
-	      $(".adr_excel").scrollLeft(left);
-	})
-
-
-	$(".adr_excel").on("scroll",function(){
-		$(".tabulator-tableholder").scrollLeft($(this).scrollLeft());
-	});
-
-	
-	
-
-    $("#excelFile").on("change", function(event) {
-    	var fileInfo =  event.target.files;
-        if(fileInfo.length > 0){
-            excelFileChange(fileInfo[0]);
-        } else {
-            fn_loadRemoveActive(); // 파일이 선택되지 않은 경우 로딩 상태 제거
-            setTimeout(() => { $(this).val(''); }, 0);  // 파일 선택 초기화
-        }
-    });
-	
-
-
-    $(document).on('click', '#btnAddrMassClose', function() {
-    	// 대량등록 닫기
-    	setAddrMassClose();
-    });
-    
-
-    $(document).on('click', '#closeBtn', function() {
-    	// 대량등록 닫기
-    	setAddrMassClose();
-    });
-    
-
-    
-
- // 엑셀등록 닫기
-	function setAddrMassClose() {
-		$tableExcel.clearData();
-		$("#excelRowTotCnt").text(0); //총건수 수정
-		$("#excelRowDupCnt").text(0); //중복건수 수정
-		$("#excelRowErrorCnt").text(0); //중복건수 수정
-		dupliPhoneDataRealList.length = 0;	// 중복 휴대폰번호 초기화
-		addrMassDupliSaveList = null;
-		
-		
-		// popup 영역
-		$tableError.clearData();
-		// 중복 카운트
-		$("#errorPopDupCnt").text(0);
-		// 에러 카운트
-		$("#errorPopErrorCnt").text(0);
-		// 
-		$("#errorPopTotCnt").text(0);
-
-		
-	}
-    
-  //#############################################################################################
-  //파일업로드 드래그앤 드롭
-  //#############################################################################################
-  var objDragAndDrop = $(".upload_area");
-  $(document).on("dragenter",".upload_area",function(e){
-    e.stopPropagation();
-    e.preventDefault();
-    //$(this).css('border', '2px solid #0B85A1');
-  });
-  $(document).on("dragover",".upload_area",function(e){
-    e.stopPropagation();
-    e.preventDefault();
-  });
-  $(document).on("drop",".upload_area",function(e){
-  	fn_loadAddActive();
-  	e.preventDefault();
-  	var files = e.originalEvent.dataTransfer.files;
-  	excelFileChange(files[0]);
-  });
-
-  $(document).on('dragenter', function (e){
-    e.stopPropagation();
-    e.preventDefault();
-  });
-  $(document).on('dragover', function (e){
-  	e.stopPropagation();
-  	e.preventDefault();
-  //objDragAndDrop.css('border', '2px dotted #0B85A1');
-  });
-  $(document).on('drop', function (e){
-    e.stopPropagation();
-    e.preventDefault();
-  });
-  //파일 드래그앤드롭 종료
-	
-	
-	
-	
-
-	//타이틀 select 선택 이벤트
-	 $('.field-selector').on('change', function() {
-		fn_loadAddActive();
-
-		setTimeout(() => { 
-			var selectedFields = [];
-			var isDuplicate = false;
-	
-			if($tableExcel.getData().length < 1){
-				alert('데이터 입력 후 선택해 주세요.');
-				$(this).val(""); 
-				fn_loadRemoveActive();
-				return false;
-			}
-			
-			// 중복체크
-			$('.field-selector').each(function() {
-				var selectedField = $(this).val();
-				if (selectedField) {
-					if (selectedFields.includes(selectedField)) {
-						alert("중복된 필드를 선택할 수 없습니다.");
-						$(this).val(""); // 중복 필드를 선택한 경우 빈 값으로 초기화
-						isDuplicate = true;
-						return false; // 반복문 종료
-					}
-					selectedFields.push(selectedField);
-				}
-			});
-	
-	
-			// 
-			updateTableFields($tableExcel);
-			
-			// 필드가 휴대폰이면 열 중복체크
-			if($(this).val() == 'addrPhoneNo'){
-				fn_phoneDupl($tableExcel);
-			}
-			fn_loadRemoveActive();
-
-		}, 0); // 지연 없이 즉시 실행되도록 0ms 지연을 설정
-		
-		
-	});
-	
-  
-
-	// 받는사람 선택삭제 버튼 처리해주기
-	$('#in_select_del').click(function(){
-		
-		if($tableExcel == null || $tableExcel == ""){
-			
-			alert("받는사람을 추가해 주세요.");
-			return false;
-		
-		}
-		
-		var selectedData = $tableExcel.getSelectedRows();
-		
-		if(selectedData == "" || selectedData == null){
-			
-			alert("삭제할 연락처를 선택해주세요.");
-			return false;
-		
-		}else{ // 선택한 Row 데이터 삭제하기
-			
-			if(confirm("선택하신 받는 사람을 삭제하시겠습니까?")){
-				
-				// 선택 데이터 삭제
-			    selectedData.forEach(row => row.delete());
-			    
-			    
-				totRows = $tableExcel.getRows().length;
-			    $("#excelRowTotCnt").text(totRows);
-			    
-				
-			}
-
-		}
-		
-	});
-
-	// 추가버튼
-	$('#btnAddrMassReg').click(function(){
-		
-		if($tableExcel.getData().length < 1){
-			alert("한 개 이상의 연락처를 입력하세요");
-			return false;
-		}
-//	 	else if (selectedData.length > 20000) {
-//	 		alert("2만줄 이상의 업로드는 데이터 부하로 업로드 할수 없습니다.");
-//	 		return false;
-//	 	}
-		
-		
-		// tableExcel 그룹의 select 요소들을 확인
-//	 	var isPhoneSelected = false;
-//	 	var isNameSelected = false;
-		var columns = $tableExcel.getColumns();
-		var isAddrPhoneNoSelected = columns.some(column => column.getField() === 'addrPhoneNo');
-
-		if (!isAddrPhoneNoSelected) {
-//			    isPhoneSelected = true;
-			alert('휴대폰이 선택되지 않았습니다.');
-			return false;
-
-		} 
-		
-		var addrData = $tableExcel.getData().map((row, index) => ({
-		    name: row.addrNm,
-		    phone: removeDash(row.addrPhoneNo),
-		    rep1: row.addrInfo1,
-		    rep2: row.addrInfo2,
-		    rep3: row.addrInfo3,
-		    rep4: row.addrInfo4,
-		}));
-
-		
-		
-		
-		
-
-		// 기존 tableL의 데이터를 가져옵니다.
-		var existingData = tableL.getData();
-		// 기존 데이터와 새로운 데이터를 합칩니다.
-		var combinedData = existingData.concat(addrData);
-		// 합쳐진 데이터를 tableL에 설정합니다.
-		tableL.setData(combinedData);
-		
-		// 미리보기 버튼 활성화
-		updateButtons(0);
-		
-		var totRows = tableL.getRows().length; 
-		updateTotCnt(totRows); //전체 데이터 갯수 구하기
-		console.log('totRows : ', totRows);
-		var smsTxtArea = $('#smsTxtArea').val();
-		if(smsTxtArea.indexOf("[*이름*]")  > -1 
-				|| smsTxtArea.indexOf("[*1*]") > -1
-				|| smsTxtArea.indexOf("[*2*]") > -1
-				|| smsTxtArea.indexOf("[*3*]") > -1
-				|| smsTxtArea.indexOf("[*4*]") > -1){
-			
-			fnReplCell();
-			
-		}else{
-			
-			//결제 금액 구하기
-		    totalPriceSum(totRows);
-			
-		} 
-		
-		setAddrMassClose();
-		$('#closeBtn').click();
-	});
-		
-  
-
-	//받는사람 전체삭제 버튼 처리
-	$('#allDel').click(function(){
-		var data = $tableExcel.getRows();	
-		$tableExcel.clearData();
-		$("#excelRowTotCnt").text(0); //총건수 수정
-		$("#excelRowDupCnt").text(0); //중복건수 수정
-		dupliPhoneDataRealList.length = 0;	// 중복 휴대폰번호 초기화
-		
-		// select box 초기화
-		$('.field-selector').each(function() { $(this).val(''); });
-		
-	});
-	
-  
-  
-});
-
-
-
-function excelFileChange(file) {
-	if (file) {
-
-		// 파일 크기 체크 (20MB)
-		const maxSize = 20 * 1024 * 1024; // 20MB in bytes
-		if (file.size > maxSize) {
-			alert('파일 크기는 20MB를 초과할 수 없습니다.');
-			return;
-		}
-		
-		fn_loadAddActive();
-		var reader = new FileReader();
-		var extension = file.name.split('.').pop().toLowerCase();
-		reader.onload = function(e) {
-			setTimeout(() => { // 파일 읽기 완료 후 실행되도록 함
-				if (extension === 'xlsx') {
-					var data = new Uint8Array(e.target.result);
-					var workbook = XLSX.read(data, {type: 'array'});
-					var firstSheet = workbook.Sheets[workbook.SheetNames[0]];
-					var jsonData = XLSX.utils.sheet_to_json(firstSheet, {header: 1});
-					processExcelData(jsonData);
-				} else if (extension === 'txt') {
-					var textData = e.target.result;
-					processTextData(textData);
-				} else {
-					alert('지원되지 않는 파일 형식입니다.');
-				}
-				fn_loadRemoveActive();
-			}, 0); // 지연 없이 즉시 실행되도록 0ms 지연을 설정
-		};
-		if (extension === 'xlsx') {
-			reader.readAsArrayBuffer(file);
-		} else if (extension === 'txt') {
-			reader.readAsText(file);
-		}
-	}
-}
-
-
-// 엑셀 데이터 처리 함수
-function processExcelData(data) {
- var keys = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
- var tableData = [];
- var totalRows = data.length - 2; // 전체 데이터 수 (1, 2행 제외)
-
-
- // 3번째 행부터 입력 
- data.slice(0).forEach((row, index) => {
-     var rowData = {};
-     keys.forEach((key, idx) => { // index 변수명 변경 (내부와 외부에서 사용되므로 충돌 방지)
-//      console.log('row[idx] : ', row[idx]);
-//          rowData[key] = row[idx] ? row[idx].trim() : ""; // 각 컬럼에 대해 기본값을 설정
-         rowData[key] = (typeof row[idx] === 'string') ? row[idx].trim() : row[idx];
-     });
-     tableData.push(rowData);
-
- });
-
- updateTable(tableData);
-}
-
-
-// 텍스트 데이터 처리 함수
-function processTextData(text) {
-	var lines = text.split('\n'); // 각 줄을 배열로 분리
-	var keys = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
-	var tableData = [];
-	
-	lines.forEach(line => {
-		var rowData = {};
-		var row = line.split(','); // 쉼표로 분리
-		keys.forEach((key, index) => {
-			rowData[key] = row[index] ? row[index].trim() : ""; // 각 컬럼에 대해 기본값을 설정
-		});
-		tableData.push(rowData);
-	});
-	
-	updateTable(tableData);
-}
-
-//공통 테이블 업데이트 함수
-function updateTable(tableData) {
-	$tableExcel.setColumns([ //Define Table Columns
- 		{formatter:"rowSelection", titleFormatter:"rowSelection",clipboard:false, headerHozAlign:"center", hozAlign:"center", headerSort:false, cellClick:function(e, cell){
- 	        cell.getRow().toggleSelect();
-	 		}
-	 	}, 
-		{formatter:"rownum", align:"center" ,title:"No", hozAlign:"center", headerHozAlign:"center", width:60},
-	 	{title:"A", field:"A", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
-	 	{title:"B", field:"B", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
-	 	{title:"C", field:"C", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
-	 	{title:"D", field:"D", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
-	 	{title:"E", field:"E", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
-	 	{title:"F", field:"F", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]}
- 	]);
-
-	$tableExcel.setData(tableData).then(() => {
-		// excelRowTotCnt 업데이트
-		document.getElementById("excelRowTotCnt").innerText = tableData.length;
-	});
-	
-	fn_loadRemoveActive();
-}
-
-
-
-/* 
-* 타이틀 select 선택할때마다 실행해서         
-* 데이터테이블 필드값 수정                  
-*/
-function updateTableFields($objTabul) {
-	var currentData = $objTabul.getData();
-	var columns = [
-		{formatter: "rowSelection", titleFormatter: "rowSelection", clipboard: false, hozAlign: "center", headerHozAlign: "center", headerSort: false, cellClick: function(e, cell) {
-			cell.getRow().toggleSelect();
-		}}
-		,{formatter:"rownum", align:"center", title:"No", hozAlign:"center", headerHozAlign:"center", width:60}
-	];
-
-	var fieldMapping = [];
-	$('.field-selector').each(function(index) {
-		var selectedField = $(this).val();
-		//  ASCII 문자 코드 사용 - 65=A, 66=B ...
-		var field = String.fromCharCode(65 + index);
-		if (selectedField) {
-			columns.push({
-				title: field
-				, field: selectedField
-				, hozAlign: "center"
-				, headerHozAlign: "center"
-// 				, editor: "input"
-				, editor: false
-				, width: 140
-				, validator: ["maxLength:100", "string"]
-			});
-			fieldMapping.push(selectedField);
-		} else {
-			columns.push({
-				title: field
-				, field: field
-				, hozAlign: "center"
-				, headerHozAlign: "center"
-				, editor: false
-// 				, editor: "input"
-				, width: 140
-				, validator: ["maxLength:100", "string"]
-			});
-			fieldMapping.push(field);
-		}
-	});
-
-	var updatedData = currentData.map(row => {
-		var newRow = {};
-		fieldMapping.forEach((field, index) => {
-			newRow[field] = row[Object.keys(row)[index]] || "";
-		});
-		return newRow;
-	});
-
-	$objTabul.setColumns(columns);
-	$objTabul.setData(updatedData);
-}
-
-
-/**
- * @ 핸드폰 중복 데이터
- * */
-function fn_phoneDupl($objTabul) {
-	 
-	$tableError.clearData();
-	
-	var data = $objTabul.getData();
-	var phoneNumberChk = false;
-	var existingNumbers = new Set(); // 배열에서 Set으로 변경
-	
-	let errorCount = 0; // 중복 번호 개수를 저장할 변수
-	let duplicateCount = 0; // 중복 번호 개수를 저장할 변수
-	
-	const errors = [];  // 오류 데이터를 저장할 배열
-	const newData = []; // 유효한 데이터만 저장할 새로운 배열
-	
-	data.forEach((row, index) => {
-		
-		const number = row.addrPhoneNo;
-		
-		// number가 null, undefined, 빈 문자열이거나 숫자인 경우 처리
-		if (!number || (typeof number === 'string' && !number.trim())){
-			console.log("number : ", number);
-			 return;
-		}
-		
-		const formattedNumber = formatPhoneNumber(number); // 번호 표준화
-		const cleanedNumber = formattedNumber.replace(/[^0-9]/g, ''); // 숫자만 남김
-		
-		if (!existingNumbers.has(cleanedNumber)) { // 중복 번호 체크
-		    if (isValidPhoneNumber(formattedNumber)) { // 유효성 검사
-		        row.addrPhoneNo = formattedNumber;
-		        existingNumbers.add(cleanedNumber); // 추가된 번호를 기존 목록에 추가
-		        newData.push(row); // 유효한 데이터만 새로운 배열에 추가
-		    } else {
-				// 오류: 유효성 통과 못함
-				errorCount++;
-				
-				errors.push({ 
-					name: row.addrNm, // 이름
-					phone: row.addrPhoneNo, // 폰번호
-					result: "오류" // 결과 메시지 추가
-				});
-		    }
-		} else {
-			// 중복
-			duplicateCount++;
-			
-			errors.push({ 
-				name: row.addrNm, // 이름
-				phone: row.addrPhoneNo, // 폰번호
-				result: "중복" // 결과 메시지 추가
-			});
-		}
-	});
-	
-	// data 배열을 newData 배열로 대체
-	data = newData;
-	
-
-	// 수정된 데이터로 테이블 업데이트
-	$objTabul.setData(data);
-	// 오류 총 카운트
-    $("#excelRowTotCnt").text($objTabul.getDataCount());
-	// 중복 카운트
-	$("#excelRowDupCnt").text(duplicateCount);
-	// 에러 카운트
-	$("#excelRowErrorCnt").text(errorCount);
-	
-	// popup 영역
-	$("#errorPopTotCnt").text($objTabul.getDataCount());
-	// 중복 카운트
-	$("#errorPopDupCnt").text(duplicateCount);
-	// 에러 카운트
-	$("#errorPopErrorCnt").text(errorCount);
-	
-	
-	$tableError.setData(errors);
-	
-	if(errorCount > 0){
-		alert('휴대폰 형식에 맞지 않는 데이터는 삭제 후 업로드 됩니다.\nex) 발송불가 특수문자, 자릿수 오류 등');
-	}
-	
-	
-	
-}
-
-function fn_dupliPopupShow(){
-
-	$("#tableExcelDupliBtn").show();
-}
-
-function makeAddrMassDupliPop(dupliPhoneDataRealList) {
-	var sHtml = "";
-	sHtml += "<div class='' style='overflow-x:auto; height:350px;'>";
-	sHtml += "<table class='tType4'>";
-	sHtml += "		<colgroup>";
-	sHtml += "			<col style='width:auto' />";
-	sHtml += "		</colgroup>";
-	sHtml += "		<thead>";
-	sHtml += "			<tr>";
-	sHtml += "				<th>중복 휴대폰번호 (" + numberWithCommas(dupliPhoneDataRealList.length) + "개)</th>";
-	sHtml += "			</tr>";
-	sHtml += "		</thead>";
-	sHtml += "		<tbody>";
-	for (var i = 0; i < dupliPhoneDataRealList.length; i++) {
-		sHtml += "		<tr>";
-		sHtml += "			<td>" + dupliPhoneDataRealList[i] + "</td>";
-		sHtml += "		</tr>";
-	}
-	sHtml += "		</tbody>";
-	sHtml += "	</table>";
-	sHtml += "	</div>";
-
-	$("#addrMassDupli_layer").html(sHtml);
-	fn_dupliPopupShow();
-	
-}
-
-
-function fn_dupliPopupShow(){
-	$('#tableExcelDupliBtn').show();
-}
-
-//한국의 핸드폰 번호 형식 검사 함수
-function isValidKoreanPhoneNumber(phone) {
-    // 하이픈(-)을 제거하고 숫자만 남긴 후 검사
-    var cleaned = phone.replace(/-/g, '');
-    // 010, 011, 016, 017, 018, 019로 시작하고 10~11자리인 경우 유효
-    var valid = /^(010|011|016|017|018|019)\d{7,8}$/.test(cleaned);
-    return valid;
-}
-
-
-
-
-// 상단 설명 더보기 
-function popMore(e){
-	$(e).closest(".pop_more_cont").toggleClass("pop_more_click");
-	
-	if($(e).closest(".pop_more_cont").is(".pop_more_click")){
-		$(e).html('숨기기');
-		$(e).append('<i></i>');
-	}else {
-		$(e).html('더보기');
-		$(e).append('<i></i>');
-	}
-}
-
-
-
-
-</script>
-
-<!-- 중복전화번호 data-tooltip:addrMassDupli_layer -->
-<div class="tooltip-wrap">
-	<div class="popup-com addrMassDupli_layer" tabindex="0" data-tooltip-con="addrMassDupli_layer" data-focus="addrMassDupli_layer" data-focus-prev="addrMassDupli_layer-close" style="width: 270px; height: 500px;">
-		<div class="popup_heading">
-			<p>중복 휴대폰번호</p>
-			<button type="button" class="tooltip-close" data-focus="addrMassDupli_layer-close" onclick="setAddrDupliClose();"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
-		</div>
-		<div class="layer_in" style="padding:20px 0px;" id="addrMassDupli_layer">
-		</div>
-		
-		<div class="popup_btn_wrap2" style="margin-top: 0px;">
-			<button type="button" class="tooltip-close" data-focus="addrMassDupli_layer-close" data-focus-next="addrMassDupli_layer">닫기</button>
-		</div>		
-		
-	</div>		
-</div>
-
-
-<!-- 주소록 상세 결과 팝업 data-tooltip:adr_popup14 -->
-	<div class="tooltip-wrap">
-		<div class="popup-com adr_layer adr_popup14" tabindex="0" data-tooltip-con="adr_popup14" data-focus="adr_popup14" data-focus-prev="adr_popu14-close" style="width: 450px;">
-			<div class="popup_heading">
-				<p>주소록 상세 결과</p>
-				<button type="button" class="tooltip-close" data-focus="adr_popup14-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
-			</div>
-			<div class="layer_in" style="padding:30px 20px;">
-				<div class="table_top">
-					<p>
-						총 <span class="c_e40000" id="errorPopTotCnt">0</span>건 
-						/ 중복 <span class="c_002c9a" id="errorPopDupCnt">0</span>건
-						/ 오류 <span class="c_002c9a" id="errorPopErrorCnt">0</span>건</p>
-						<button type="button" class="excel_btn btnType" id="errorExcelBtn"><i class="downroad"></i>엑셀 다운로드</button>
-				</div>
-				<div class="tb_wrap adr_list" id="tabulator_error">
-				<!-- $tableError 참고  -->
-                </div>
-                <ul class="cf_text_ul">
-                    <li>*중복번호는 하나의 번호만 등록됩니다.</li>
-                    <li>*휴대폰 형식에 맞지 않는 데이터는 삭제 후 업로드 됩니다.</li>
-                    <li>ex) 발송불가 특수문자, 자릿수 오류 등</li>
-                </ul>
-                <div class="popup_btn_wrap2">
-<!--                     <button type="button">저장</button> -->
-                    <button type="button" class="tooltip-close" data-focus="adr_popup14-close" data-focus-next="adr_popup14">닫기</button>                      
-                </div>
-			</div>
-		</div>
-	</div>
-
-<!--// 중복전화번호 팝업 -->
-				<div class="popup_heading">
-					<p>엑셀 불러오기</p>
-					<button type="button" class="tooltip-close" id="closeBtn" data-focus="popup02-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
-			   </div>
-				<div class="layer_in" style="padding: 25px 30px;">
-<!-- 					<div class="list_tab_wrap2"> -->
-						<!-- tab button -->
-<!-- 						<ul class="list_tab" id="tbTabl">  -->
-<!-- 							<li class="tab active" data-tabul="tableExcel"><button type="button" onclick="popupTab(this,'1'); fn_tabToggle('1');">엑셀입력</button></li> -->
-<!-- 							<li class="tab" data-tabul="tableClip"><button type="button" onclick="popupTab(this,'2'); fn_tabToggle('2');">붙여넣기</button></li> -->
-<!-- 							<li class="tab" data-tabul="tableSelf"><button type="button" onclick="popupTab(this,'3'); fn_tabToggle('3');">직접입력</button></li> -->
-<!-- 						</ul>// tab button -->
-<!-- 					</div> -->
-					<!-- 엑셀입력 -->
-					<div class="popCont current pop_more_cont" id="popCont_1">
-						<div class="titBox">
-							<p>- 주소록은 한 번에 최대 30만건까지 등록(EXCEL파일, 최대용량 20MB) 가능합니다. </p>
-							<p>- 엑셀 파일에 비밀번호 설정, 제한된 보기, 수식 등이 설정되어 있는 경우 업로드가 불가합니다.</p>
-							<p>- 구분선(|), 역슬래시(\, ₩), 큰따옴표(") 등 발송불가 특수문자는 저장되지 않습니다.</p>
-							<p>- 이름 200byte, [*1*]~[*4*] 200byte, 메모 250byte까지 입력 가능합니다.</p>
-							<p>- 주소록 등록이 어려우신 경우에는 <a href="<c:url value='/web/mjon/addragency/selectAddrAgencyList.do'/>" style="font-weight: bold; color: blue;">주소록 입력대행</a> 메뉴를 이용하실 수 있습니다. </p>
-						</div>
-						<div class="pop_more_wrap">
-								<button type="button" class="pop_more" onclick="popMore(this);">더보기<i></i></button>
-						</div>
-					</div><!--// 엑셀입력 -->
-
-					<!-- 공통 -->
-					<div>
-		                <table class="layer_tType1">
-		                    <caption>엑셀입력 표</caption>
-		                    <colgroup>
-		                        <col style="width: 95px">
-		                        <col style="width: auto">
-		                    </colgroup>
-		                    <tbody>
-		                        <tr>
-		                            <!-- <th>그룹 선택</th>
-		                            <td>
-		                                <label for="" class="label">그룹 선택</label>
-		                                <select id="addrGrpIdInfo" name="addrGrpIdInfo">
-		                                </select>
-	                                    <label for="" class="label">그룹명 입력</label>
-	                                    <input type="text" id="addrGrpNm" name="addrGrpNm" placeholder="새 그룹명을 입력해주세요." onfocus="this.placeholder=''" onblur="this.placeholder='새 그룹명을 입력해주세요.'"class="inputLight" style="width: 300px;">
-		                                <input type="file" id="excelFile" accept=".xls, .xlsx, .txt" style="display:none"/>
-		                                <button type="button" class="excel_btn2 btnType c3"><i class="uproad"></i>엑셀, TXT파일 업로드</button>
-		                            </td> -->
-		                            <td colspan="2" style="padding:20px 0;">
-		                            	<div class="file_upload_wrap" style="width:100%;display:flex;">
-						                	<div class="file_add upload_area">
-												<p><img src="/publish/images/content/file_add.png" alt="파일 붙여넣기">마우스로 엑셀, TXT파일을 여기에 끌어다 놓으세요</p>
-											</div>
-											<input type="file" id="excelFile" accept=".xls, .xlsx, .txt" style="display:none"/>
-					                        <button type="button" class="excel_btn2 btnType c3"><i class="uproad"></i>엑셀, TXT파일 업로드</button>
-						                </div>
-		                            </td>
-		                        </tr>
-		                    </tbody>
-		                </table>
-	                </div>
-	                
-					
-					<div class="excel_middle2">
-						<p>
-							총 <span class="c_e40000 fwBold" id="excelRowTotCnt">0</span>건 
-							/ 중복 <span class="c_002c9a fwBold" id="excelRowDupCnt">0</span>건
-							/ 오류 <span class="c_002c9a fwBold" id="excelRowErrorCnt">0</span>건
-							<button type="button" class="btn_list_detail" data-tooltip="adr_popup14"><img src="/publish/images/search.png"></button>
-						</p>
-<!-- 							&nbsp;  -->
-<!-- 							<button type="button" class="btnType btnType6" data-tooltip="addrMassDupli_layer" id="tableExcelDupliBtn">중복번호</button> -->
-<!-- 							&nbsp; -->
-<!-- 							<button type="button" class="btnType btnType6" data-tooltip="addrMassSaveDupli_layer" onclick="GetAddrMassSaveDupli()" id="btnAddrMassSaveDupli">중복번호</button> -->
-						</p>
-<!-- 						<button type="button" class="btnType btnType6 addCallToF">번호추가</button> -->
-					</div>
-					
-					
-					
-
-					<div class="adr_excel" style="margin-top: 13px; overflow-x:auto;">
-<!--                     <div class="adr_excel" style="margin-top: 13px;"> -->
-                        <!-- thead -->
-                        <div class="adr_hd select_adr_hd" data-group="tableExcel">
-                            <div style="width: 100px;"></div>
-                            <div style="width: 140px;">
-                            	<label for="" class="label"></label>
-								<select class="field-selector">
-									<option value="">선택하기</option>
-									<option value="addrNm">이름</option>
-									<option value="addrPhoneNo">휴대폰</option>
-									<option value="addrInfo1">[*1*]</option>
-									<option value="addrInfo2">[*2*]</option>
-									<option value="addrInfo3">[*3*]</option>
-									<option value="addrInfo4">[*4*]</option>
-<!-- 									<option value="addrComment">메모</option> -->
-								</select>
-                            </div>
-                            <div style="width: 140px;">
-                                <label for="" class="label"></label>
-								<select class="field-selector">
-									<option value="">선택하기</option>
-									<option value="addrNm">이름</option>
-									<option value="addrPhoneNo">휴대폰</option>
-									<option value="addrInfo1">[*1*]</option>
-									<option value="addrInfo2">[*2*]</option>
-									<option value="addrInfo3">[*3*]</option>
-									<option value="addrInfo4">[*4*]</option>
-<!-- 									<option value="addrComment">메모</option> -->
-								</select>
-                            </div>
-                            <div style="width: 140px;">
-                                <label for="" class="label"></label>
-								<select class="field-selector">
-									<option value="">선택하기</option>
-									<option value="addrNm">이름</option>
-									<option value="addrPhoneNo">휴대폰</option>
-									<option value="addrInfo1">[*1*]</option>
-									<option value="addrInfo2">[*2*]</option>
-									<option value="addrInfo3">[*3*]</option>
-									<option value="addrInfo4">[*4*]</option>
-<!-- 									<option value="addrComment">메모</option> -->
-								</select>
-                            </div>
-                            <div style="width: 140px;">
-                                <label for="" class="label"></label>
-								<select class="field-selector">
-									<option value="">선택하기</option>
-									<option value="addrNm">이름</option>
-									<option value="addrPhoneNo">휴대폰</option>
-									<option value="addrInfo1">[*1*]</option>
-									<option value="addrInfo2">[*2*]</option>
-									<option value="addrInfo3">[*3*]</option>
-									<option value="addrInfo4">[*4*]</option>
-<!-- 									<option value="addrComment">메모</option> -->
-								</select>
-                            </div>
-                            <div style="width: 140px;">
-                                <label for="" class="label"></label>
-								<select class="field-selector">
-									<option value="">선택하기</option>
-									<option value="addrNm">이름</option>
-									<option value="addrPhoneNo">휴대폰</option>
-									<option value="addrInfo1">[*1*]</option>
-									<option value="addrInfo2">[*2*]</option>
-									<option value="addrInfo3">[*3*]</option>
-									<option value="addrInfo4">[*4*]</option>
-<!-- 									<option value="addrComment">메모</option> -->
-								</select>
-                            </div>
-                            <div style="width: 140px;">
-                                <label for="" class="label"></label>
-								<select class="field-selector">
-									<option value="">선택하기</option>
-									<option value="addrNm">이름</option>
-									<option value="addrPhoneNo">휴대폰</option>
-									<option value="addrInfo1">[*1*]</option>
-									<option value="addrInfo2">[*2*]</option>
-									<option value="addrInfo3">[*3*]</option>
-									<option value="addrInfo4">[*4*]</option>
-<!-- 									<option value="addrComment">메모</option> -->
-								</select>
-                            </div>
-                            <!-- <div style="width: 125px;">
-                                <label for="" class="label"></label>
-								<select class="field-selector">
-									<option value="">선택하기</option>
-									<option value="addrNm">이름</option>
-									<option value="addrPhoneNo">휴대폰</option>
-									<option value="addrInfo1">[*1*]</option>
-									<option value="addrInfo2">[*2*]</option>
-									<option value="addrInfo3">[*3*]</option>
-									<option value="addrInfo4">[*4*]</option>
-									<option value="addrComment">메모</option>
-								</select>
-                            </div> -->
-                        </div>
-                     </div>					
-					
-					<div class="drag_drop_wrap callList_includ_box" id="tabulator_excel">
-<!-- 						<img src="/publish/images/content/excel.jpg" style="width: 100%;"> -->
-					</div>
-					<div class="excel_middle">
-						<div class="select_btnWrap clearfix">
-							<div>
-								<button type="button" id="allDel"><i class="remove_img"></i>전체삭제</button>
-								<button type="button" id="in_select_del"><i class="remove_img"></i>선택삭제</button>
-							</div>
-	
-						</div>
-					</div><!--// 공통 -->     
-	
-					<!-- 붙여놓기 설명 -->
-<!-- 	                <div class="req_area"> -->
-<!-- 	                  	<div class="text_box"> -->
-<!-- 	                    	- 휴대폰 번호가 입력된 txt 파일을 열어 복사(Ctrl+c) + 붙여넣기(Ctrl+v)로도 입력하실 수 있습니다.<br> -->
-<!-- 							- 휴대폰 번호는 필수입력 항목입니다.<br> -->
-<!-- 							- 이름,휴대폰 번호,[*1*],[*2*],[*3*],[*4*],메모 순서대로 입력해주세요.(예 : 010-1234-5678,홍길동,변수1…메모)<br> -->
-<!-- 							- 이름은 24byte, [*1*]~[*4*] 40byte, 메모는 250byte까지 입력 가능합니다.<br> -->
-<!-- 							- '오류 검사'를 통해 등록된 데이터에 전화번호 입력 오류를 확인하실 수 있습니다. -->
-<!-- 						</div> -->
-<!-- 	                </div> -->
-					<div class="popup_btn_wrap2" style="margin: 0 auto 30px auto;">
-						<button type="button" id="btnAddrMassReg">추가</button>
-						<button type="button" id="btnAddrMassClose" class="tooltip-close" data-focus="adr_popup01-close"  data-focus-next="popup02">닫기</button>                      
-					</div>
-				
+<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+
+<script type="text/javascript" src="<c:url value='/publish/js/content.js'/>"></script>
+
+<script type="text/javascript">
+
+var $tableExcel = null; //엑셀입력 탭
+var $tableError = null; //엑셀입력 탭
+$(document).ready(function(){
+
+	//Tabulator AJAX Data Loading
+	$tableError = new Tabulator("#tabulator_error", {
+		height:"255px",
+		width:"100%",
+	    layout:"fitColumns",
+	    autoColumns:false,
+	    headerHozAlign:"center", 
+	    validationMode:"highlight",
+	    clipboard:false,
+	    clipboardCopySelector:"table",
+	    clipboardPasteAction:"insert", // insert, update, replace
+	    placeholder:"등록 팝업에서 휴대폰을 선택 후 확인해주세요.", //fit columns to width of table (optional)
+	 	columns:[ //Define Table Columns
+		 	{title:"이름", field:"name", hozAlign:"center", headerHozAlign: "center", width:125},
+		 	{title:"휴대폰", field:"phone", hozAlign:"center", headerHozAlign: "center", width:158},
+		 	{title:"미등록 결과", field:"result", hozAlign:"center", headerHozAlign: "center", width:125}
+	 	]
+	});
+	
+
+	//Tabulator AJAX Data Loading
+	$tableExcel = new Tabulator("#tabulator_excel", {
+		height:"255px",
+		width:"100%",
+	    layout:"fitColumns",
+	    autoColumns:false,
+	    headerHozAlign:"center", 
+	    validationMode:"highlight",
+	    clipboard:false,
+	    clipboardCopySelector:"table",
+	    clipboardPasteAction:"insert", // insert, update, replace
+	    placeholder:"Excel 파일을 업로드 해주세요.", //fit columns to width of table (optional)
+	 	columns:[ //Define Table Columns
+	 		{formatter:"rowSelection", titleFormatter:"rowSelection",clipboard:false, headerHozAlign:"center", hozAlign:"center", headerSort:false, cellClick:function(e, cell){
+	 	        cell.getRow().toggleSelect();
+		 		}
+		 	}, 
+			{formatter:"rownum", align:"center" ,title:"No", hozAlign:"center", headerHozAlign:"center", width:40},
+		 	{title:"A", field:"A", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
+		 	{title:"B", field:"B", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
+		 	{title:"C", field:"C", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
+		 	{title:"D", field:"D", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
+		 	{title:"E", field:"E", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
+		 	{title:"F", field:"F", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]}
+	 	],
+	 	validationFailed:function(cell, value, parameters){ // 유효성 체크 함수 
+	        var valid = cell.isValid();
+	 		if(!valid){
+	 			alert("양식에 맞지 않는 정보가 입력되었습니다.");
+	 			
+	 			//해당 셀 데이터 삭제
+	 			cell.setValue("");
+	 		}
+	 		return value % parameters.phone;
+	    },
+	});
+	
+	
+    
+	
+	// 타뷸레이터 width값 변경 시 위에 select width 값 변경
+	var titleArray = ["A","B","C","D","E","F"];
+
+	$tableExcel.on("columnWidth",function(column){
+		var titleIndex = titleArray.indexOf(column._column.definition.title);
+		titleIndex += 1;
+		if(titleIndex != 0){
+			$('.select_adr_hd>div').eq(titleIndex).css('width', column._column.width);
+		}else{
+			$('.select_adr_hd>div').eq(0).css('width', column._column.width + 40);
+		}
+	});
+
+	$tableExcel.on("scrollHorizontal",function(left){
+	      $(".adr_excel").scrollLeft(left);
+	})
+
+
+	$(".adr_excel").on("scroll",function(){
+		$(".tabulator-tableholder").scrollLeft($(this).scrollLeft());
+	});
+
+	
+	
+
+    $("#excelFile").on("change", function(event) {
+    	var fileInfo =  event.target.files;
+        if(fileInfo.length > 0){
+            excelFileChange(fileInfo[0]);
+        } else {
+            fn_loadRemoveActive(); // 파일이 선택되지 않은 경우 로딩 상태 제거
+            setTimeout(() => { $(this).val(''); }, 0);  // 파일 선택 초기화
+        }
+    });
+	
+
+
+    $(document).on('click', '#btnAddrMassClose', function() {
+
+    	$('.field-selector').each(function() { $(this).val(''); });
+    	setAddrMassClose();
+    });
+    
+
+    $(document).on('click', '#closeBtn', function() {
+    	// 대량등록 닫기
+    	setAddrMassClose();
+    });
+    
+
+    
+
+ // 엑셀등록 닫기
+	function setAddrMassClose() {
+		$tableExcel.clearData();
+		$("#excelRowTotCnt").text(0); //총건수 수정
+		$("#excelRowDupCnt").text(0); //중복건수 수정
+		$("#excelRowErrorCnt").text(0); //중복건수 수정
+		dupliPhoneDataRealList.length = 0;	// 중복 휴대폰번호 초기화
+		addrMassDupliSaveList = null;
+		
+		
+		// popup 영역
+		$tableError.clearData();
+		// 중복 카운트
+		$("#errorPopDupCnt").text(0);
+		// 에러 카운트
+		$("#errorPopErrorCnt").text(0);
+		// 
+		$("#errorPopTotCnt").text(0);
+
+		
+	}
+    
+  //#############################################################################################
+  //파일업로드 드래그앤 드롭
+  //#############################################################################################
+  var objDragAndDrop = $(".upload_area");
+  $(document).on("dragenter",".upload_area",function(e){
+    e.stopPropagation();
+    e.preventDefault();
+    //$(this).css('border', '2px solid #0B85A1');
+  });
+  $(document).on("dragover",".upload_area",function(e){
+    e.stopPropagation();
+    e.preventDefault();
+  });
+  $(document).on("drop",".upload_area",function(e){
+  	fn_loadAddActive();
+  	e.preventDefault();
+  	var files = e.originalEvent.dataTransfer.files;
+  	excelFileChange(files[0]);
+  });
+
+  $(document).on('dragenter', function (e){
+    e.stopPropagation();
+    e.preventDefault();
+  });
+  $(document).on('dragover', function (e){
+  	e.stopPropagation();
+  	e.preventDefault();
+  //objDragAndDrop.css('border', '2px dotted #0B85A1');
+  });
+  $(document).on('drop', function (e){
+    e.stopPropagation();
+    e.preventDefault();
+  });
+  //파일 드래그앤드롭 종료
+	
+	
+	
+	
+
+	//타이틀 select 선택 이벤트
+	 $('.field-selector').on('change', function() {
+		fn_loadAddActive();
+
+		setTimeout(() => { 
+			var selectedFields = [];
+			var isDuplicate = false;
+	
+			if($tableExcel.getData().length < 1){
+				alert('데이터 입력 후 선택해 주세요.');
+				$(this).val(""); 
+				fn_loadRemoveActive();
+				return false;
+			}
+			
+			// 중복체크
+			$('.field-selector').each(function() {
+				var selectedField = $(this).val();
+				if (selectedField) {
+					if (selectedFields.includes(selectedField)) {
+						alert("중복된 필드를 선택할 수 없습니다.");
+						$(this).val(""); // 중복 필드를 선택한 경우 빈 값으로 초기화
+						isDuplicate = true;
+						return false; // 반복문 종료
+					}
+					selectedFields.push(selectedField);
+				}
+			});
+	
+	
+			// 
+			updateTableFields($tableExcel);
+			
+			// 필드가 휴대폰이면 열 중복체크
+			if($(this).val() == 'addrPhoneNo'){
+				fn_phoneDupl($tableExcel);
+			}
+			fn_loadRemoveActive();
+
+		}, 0); // 지연 없이 즉시 실행되도록 0ms 지연을 설정
+		
+		
+	});
+	
+  
+
+	// 받는사람 선택삭제 버튼 처리해주기
+	$('#in_select_del').click(function(){
+		
+		if($tableExcel == null || $tableExcel == ""){
+			
+			alert("받는사람을 추가해 주세요.");
+			return false;
+		
+		}
+		
+		var selectedData = $tableExcel.getSelectedRows();
+		
+		if(selectedData == "" || selectedData == null){
+			
+			alert("삭제할 연락처를 선택해주세요.");
+			return false;
+		
+		}else{ // 선택한 Row 데이터 삭제하기
+			
+			if(confirm("선택하신 받는 사람을 삭제하시겠습니까?")){
+				
+				// 선택 데이터 삭제
+			    selectedData.forEach(row => row.delete());
+			    
+			    
+				totRows = $tableExcel.getRows().length;
+			    $("#excelRowTotCnt").text(totRows);
+			    
+				
+			}
+
+		}
+		
+	});
+
+	// 추가버튼
+	$('#btnAddrMassReg').click(function(){
+		
+		if($tableExcel.getData().length < 1){
+			alert("한 개 이상의 연락처를 입력하세요");
+			return false;
+		}
+//	 	else if (selectedData.length > 20000) {
+//	 		alert("2만줄 이상의 업로드는 데이터 부하로 업로드 할수 없습니다.");
+//	 		return false;
+//	 	}
+		
+		
+		// tableExcel 그룹의 select 요소들을 확인
+//	 	var isPhoneSelected = false;
+//	 	var isNameSelected = false;
+		var columns = $tableExcel.getColumns();
+		var isAddrPhoneNoSelected = columns.some(column => column.getField() === 'addrPhoneNo');
+
+		if (!isAddrPhoneNoSelected) {
+//			    isPhoneSelected = true;
+			alert('휴대폰이 선택되지 않았습니다.');
+			return false;
+
+		} 
+		
+		var addrData = $tableExcel.getData().map((row, index) => ({
+		    name: row.addrNm,
+		    phone: removeDash(row.addrPhoneNo),
+		    rep1: row.addrInfo1,
+		    rep2: row.addrInfo2,
+		    rep3: row.addrInfo3,
+		    rep4: row.addrInfo4,
+		}));
+
+		
+		
+		
+		
+
+		// 기존 tableL의 데이터를 가져옵니다.
+		var existingData = tableL.getData();
+		// 기존 데이터와 새로운 데이터를 합칩니다.
+		var combinedData = existingData.concat(addrData);
+		// 합쳐진 데이터를 tableL에 설정합니다.
+		tableL.setData(combinedData);
+		
+		// 미리보기 버튼 활성화
+		updateButtons(0);
+		
+		var totRows = tableL.getRows().length; 
+		updateTotCnt(totRows); //전체 데이터 갯수 구하기
+		console.log('totRows : ', totRows);
+		var smsTxtArea = $('#smsTxtArea').val();
+		if(smsTxtArea.indexOf("[*이름*]")  > -1 
+				|| smsTxtArea.indexOf("[*1*]") > -1
+				|| smsTxtArea.indexOf("[*2*]") > -1
+				|| smsTxtArea.indexOf("[*3*]") > -1
+				|| smsTxtArea.indexOf("[*4*]") > -1){
+			
+			fnReplCell();
+			
+		}else{
+			
+			//결제 금액 구하기
+		    totalPriceSum(totRows);
+			
+		} 
+		
+		setAddrMassClose();
+		$('.field-selector').each(function() { $(this).val(''); });
+		$('#closeBtn').click();
+	});
+		
+  
+
+	//받는사람 전체삭제 버튼 처리
+	$('#allDel').click(function(){
+		var data = $tableExcel.getRows();	
+		$tableExcel.clearData();
+		$("#excelRowTotCnt").text(0); //총건수 수정
+		$("#excelRowDupCnt").text(0); //중복건수 수정
+		dupliPhoneDataRealList.length = 0;	// 중복 휴대폰번호 초기화
+		
+		// select box 초기화
+		$('.field-selector').each(function() { $(this).val(''); });
+		
+	});
+	
+  
+  
+});
+
+
+
+function excelFileChange(file) {
+	if (file) {
+
+		// 파일 크기 체크 (20MB)
+		const maxSize = 20 * 1024 * 1024; // 20MB in bytes
+		if (file.size > maxSize) {
+			alert('파일 크기는 20MB를 초과할 수 없습니다.');
+			return;
+		}
+		
+		fn_loadAddActive();
+		var reader = new FileReader();
+		var extension = file.name.split('.').pop().toLowerCase();
+		reader.onload = function(e) {
+			setTimeout(() => { // 파일 읽기 완료 후 실행되도록 함
+				if (extension === 'xlsx') {
+					var data = new Uint8Array(e.target.result);
+					var workbook = XLSX.read(data, {type: 'array'});
+					var firstSheet = workbook.Sheets[workbook.SheetNames[0]];
+					var jsonData = XLSX.utils.sheet_to_json(firstSheet, {header: 1});
+					processExcelData(jsonData);
+				} else if (extension === 'txt') {
+					var textData = e.target.result;
+					processTextData(textData);
+				} else {
+					alert('지원되지 않는 파일 형식입니다.');
+				}
+				fn_loadRemoveActive();
+			}, 0); // 지연 없이 즉시 실행되도록 0ms 지연을 설정
+		};
+		if (extension === 'xlsx') {
+			reader.readAsArrayBuffer(file);
+		} else if (extension === 'txt') {
+			reader.readAsText(file);
+		}
+	}
+}
+
+
+// 엑셀 데이터 처리 함수
+function processExcelData(data) {
+ var keys = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
+ var tableData = [];
+ var totalRows = data.length - 2; // 전체 데이터 수 (1, 2행 제외)
+
+
+ // 3번째 행부터 입력 
+ data.slice(0).forEach((row, index) => {
+     var rowData = {};
+     keys.forEach((key, idx) => { // index 변수명 변경 (내부와 외부에서 사용되므로 충돌 방지)
+//      console.log('row[idx] : ', row[idx]);
+//          rowData[key] = row[idx] ? row[idx].trim() : ""; // 각 컬럼에 대해 기본값을 설정
+         rowData[key] = (typeof row[idx] === 'string') ? row[idx].trim() : row[idx];
+     });
+     tableData.push(rowData);
+
+ });
+
+ updateTable(tableData);
+}
+
+
+// 텍스트 데이터 처리 함수
+function processTextData(text) {
+	var lines = text.split('\n'); // 각 줄을 배열로 분리
+	var keys = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
+	var tableData = [];
+	
+	lines.forEach(line => {
+		var rowData = {};
+		var row = line.split(','); // 쉼표로 분리
+		keys.forEach((key, index) => {
+			rowData[key] = row[index] ? row[index].trim() : ""; // 각 컬럼에 대해 기본값을 설정
+		});
+		tableData.push(rowData);
+	});
+	
+	updateTable(tableData);
+}
+
+//공통 테이블 업데이트 함수
+function updateTable(tableData) {
+	$tableExcel.setColumns([ //Define Table Columns
+ 		{formatter:"rowSelection", titleFormatter:"rowSelection",clipboard:false, headerHozAlign:"center", hozAlign:"center", headerSort:false, cellClick:function(e, cell){
+ 	        cell.getRow().toggleSelect();
+	 		}
+	 	}, 
+		{formatter:"rownum", align:"center" ,title:"No", hozAlign:"center", headerHozAlign:"center", width:60},
+	 	{title:"A", field:"A", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
+	 	{title:"B", field:"B", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
+	 	{title:"C", field:"C", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
+	 	{title:"D", field:"D", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
+	 	{title:"E", field:"E", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]},
+	 	{title:"F", field:"F", hozAlign:"center", headerHozAlign: "center", width:140, validator:["maxLength:100", "string"]}
+ 	]);
+
+	$tableExcel.setData(tableData).then(() => {
+		// excelRowTotCnt 업데이트
+		document.getElementById("excelRowTotCnt").innerText = tableData.length;
+	});
+	
+	fn_loadRemoveActive();
+}
+
+
+
+/* 
+* 타이틀 select 선택할때마다 실행해서         
+* 데이터테이블 필드값 수정                  
+*/
+function updateTableFields($objTabul) {
+	var currentData = $objTabul.getData();
+	var columns = [
+		{formatter: "rowSelection", titleFormatter: "rowSelection", clipboard: false, hozAlign: "center", headerHozAlign: "center", headerSort: false, cellClick: function(e, cell) {
+			cell.getRow().toggleSelect();
+		}}
+		,{formatter:"rownum", align:"center", title:"No", hozAlign:"center", headerHozAlign:"center", width:60}
+	];
+
+	var fieldMapping = [];
+	$('.field-selector').each(function(index) {
+		var selectedField = $(this).val();
+		//  ASCII 문자 코드 사용 - 65=A, 66=B ...
+		var field = String.fromCharCode(65 + index);
+		if (selectedField) {
+			columns.push({
+				title: field
+				, field: selectedField
+				, hozAlign: "center"
+				, headerHozAlign: "center"
+// 				, editor: "input"
+				, editor: false
+				, width: 140
+				, validator: ["maxLength:100", "string"]
+			});
+			fieldMapping.push(selectedField);
+		} else {
+			columns.push({
+				title: field
+				, field: field
+				, hozAlign: "center"
+				, headerHozAlign: "center"
+				, editor: false
+// 				, editor: "input"
+				, width: 140
+				, validator: ["maxLength:100", "string"]
+			});
+			fieldMapping.push(field);
+		}
+	});
+
+	var updatedData = currentData.map(row => {
+		var newRow = {};
+		fieldMapping.forEach((field, index) => {
+			newRow[field] = row[Object.keys(row)[index]] || "";
+		});
+		return newRow;
+	});
+
+	$objTabul.setColumns(columns);
+	$objTabul.setData(updatedData);
+}
+
+
+/**
+ * @ 핸드폰 중복 데이터
+ * */
+function fn_phoneDupl($objTabul) {
+	 
+	$tableError.clearData();
+	
+	var data = $objTabul.getData();
+	var phoneNumberChk = false;
+	var existingNumbers = new Set(); // 배열에서 Set으로 변경
+	
+	let errorCount = 0; // 중복 번호 개수를 저장할 변수
+	let duplicateCount = 0; // 중복 번호 개수를 저장할 변수
+	
+	const errors = [];  // 오류 데이터를 저장할 배열
+	const newData = []; // 유효한 데이터만 저장할 새로운 배열
+	
+	data.forEach((row, index) => {
+		
+		const number = row.addrPhoneNo;
+		
+		// number가 null, undefined, 빈 문자열이거나 숫자인 경우 처리
+		if (!number || (typeof number === 'string' && !number.trim())){
+			console.log("number : ", number);
+			 return;
+		}
+		
+		const formattedNumber = formatPhoneNumber(number); // 번호 표준화
+		const cleanedNumber = formattedNumber.replace(/[^0-9]/g, ''); // 숫자만 남김
+		
+		if (!existingNumbers.has(cleanedNumber)) { // 중복 번호 체크
+		    if (isValidPhoneNumber(formattedNumber)) { // 유효성 검사
+		        row.addrPhoneNo = formattedNumber;
+		        existingNumbers.add(cleanedNumber); // 추가된 번호를 기존 목록에 추가
+		        newData.push(row); // 유효한 데이터만 새로운 배열에 추가
+		    } else {
+				// 오류: 유효성 통과 못함
+				errorCount++;
+				
+				errors.push({ 
+					name: row.addrNm, // 이름
+					phone: row.addrPhoneNo, // 폰번호
+					result: "오류" // 결과 메시지 추가
+				});
+		    }
+		} else {
+			// 중복
+			duplicateCount++;
+			
+			errors.push({ 
+				name: row.addrNm, // 이름
+				phone: row.addrPhoneNo, // 폰번호
+				result: "중복" // 결과 메시지 추가
+			});
+		}
+	});
+	
+	// data 배열을 newData 배열로 대체
+	data = newData;
+	
+
+	// 수정된 데이터로 테이블 업데이트
+	$objTabul.setData(data);
+	// 오류 총 카운트
+    $("#excelRowTotCnt").text($objTabul.getDataCount());
+	// 중복 카운트
+	$("#excelRowDupCnt").text(duplicateCount);
+	// 에러 카운트
+	$("#excelRowErrorCnt").text(errorCount);
+	
+	// popup 영역
+	$("#errorPopTotCnt").text($objTabul.getDataCount());
+	// 중복 카운트
+	$("#errorPopDupCnt").text(duplicateCount);
+	// 에러 카운트
+	$("#errorPopErrorCnt").text(errorCount);
+	
+	
+	$tableError.setData(errors);
+	
+	if(errorCount > 0){
+		alert('휴대폰 형식에 맞지 않는 데이터는 삭제 후 업로드 됩니다.\nex) 발송불가 특수문자, 자릿수 오류 등');
+	}
+	
+	
+	
+}
+
+function fn_dupliPopupShow(){
+
+	$("#tableExcelDupliBtn").show();
+}
+
+function makeAddrMassDupliPop(dupliPhoneDataRealList) {
+	var sHtml = "";
+	sHtml += "<div class='' style='overflow-x:auto; height:350px;'>";
+	sHtml += "<table class='tType4'>";
+	sHtml += "		<colgroup>";
+	sHtml += "			<col style='width:auto' />";
+	sHtml += "		</colgroup>";
+	sHtml += "		<thead>";
+	sHtml += "			<tr>";
+	sHtml += "				<th>중복 휴대폰번호 (" + numberWithCommas(dupliPhoneDataRealList.length) + "개)</th>";
+	sHtml += "			</tr>";
+	sHtml += "		</thead>";
+	sHtml += "		<tbody>";
+	for (var i = 0; i < dupliPhoneDataRealList.length; i++) {
+		sHtml += "		<tr>";
+		sHtml += "			<td>" + dupliPhoneDataRealList[i] + "</td>";
+		sHtml += "		</tr>";
+	}
+	sHtml += "		</tbody>";
+	sHtml += "	</table>";
+	sHtml += "	</div>";
+
+	$("#addrMassDupli_layer").html(sHtml);
+	fn_dupliPopupShow();
+	
+}
+
+
+function fn_dupliPopupShow(){
+	$('#tableExcelDupliBtn').show();
+}
+
+//한국의 핸드폰 번호 형식 검사 함수
+function isValidKoreanPhoneNumber(phone) {
+    // 하이픈(-)을 제거하고 숫자만 남긴 후 검사
+    var cleaned = phone.replace(/-/g, '');
+    // 010, 011, 016, 017, 018, 019로 시작하고 10~11자리인 경우 유효
+    var valid = /^(010|011|016|017|018|019)\d{7,8}$/.test(cleaned);
+    return valid;
+}
+
+
+
+
+// 상단 설명 더보기 
+function popMore(e){
+	$(e).closest(".pop_more_cont").toggleClass("pop_more_click");
+	
+	if($(e).closest(".pop_more_cont").is(".pop_more_click")){
+		$(e).html('숨기기');
+		$(e).append('<i></i>');
+	}else {
+		$(e).html('더보기');
+		$(e).append('<i></i>');
+	}
+}
+
+
+
+
+</script>
+
+<!-- 중복전화번호 data-tooltip:addrMassDupli_layer -->
+<div class="tooltip-wrap">
+	<div class="popup-com addrMassDupli_layer" tabindex="0" data-tooltip-con="addrMassDupli_layer" data-focus="addrMassDupli_layer" data-focus-prev="addrMassDupli_layer-close" style="width: 270px; height: 500px;">
+		<div class="popup_heading">
+			<p>중복 휴대폰번호</p>
+			<button type="button" class="tooltip-close" data-focus="addrMassDupli_layer-close" onclick="setAddrDupliClose();"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+		</div>
+		<div class="layer_in" style="padding:20px 0px;" id="addrMassDupli_layer">
+		</div>
+		
+		<div class="popup_btn_wrap2" style="margin-top: 0px;">
+			<button type="button" class="tooltip-close" data-focus="addrMassDupli_layer-close" data-focus-next="addrMassDupli_layer">닫기</button>
+		</div>		
+		
+	</div>		
+</div>
+
+
+<!-- 주소록 상세 결과 팝업 data-tooltip:adr_popup14 -->
+	<div class="tooltip-wrap">
+		<div class="popup-com adr_layer adr_popup14" tabindex="0" data-tooltip-con="adr_popup14" data-focus="adr_popup14" data-focus-prev="adr_popu14-close" style="width: 450px;">
+			<div class="popup_heading">
+				<p>주소록 상세 결과</p>
+				<button type="button" class="tooltip-close" data-focus="adr_popup14-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+			</div>
+			<div class="layer_in" style="padding:30px 20px;">
+				<div class="table_top">
+					<p>
+						총 <span class="c_e40000" id="errorPopTotCnt">0</span>건 
+						/ 중복 <span class="c_002c9a" id="errorPopDupCnt">0</span>건
+						/ 오류 <span class="c_002c9a" id="errorPopErrorCnt">0</span>건</p>
+						<button type="button" class="excel_btn btnType" id="errorExcelBtn"><i class="downroad"></i>엑셀 다운로드</button>
+				</div>
+				<div class="tb_wrap adr_list" id="tabulator_error">
+				<!-- $tableError 참고  -->
+                </div>
+                <ul class="cf_text_ul">
+                    <li>*중복번호는 하나의 번호만 등록됩니다.</li>
+                    <li>*휴대폰 형식에 맞지 않는 데이터는 삭제 후 업로드 됩니다.</li>
+                    <li>ex) 발송불가 특수문자, 자릿수 오류 등</li>
+                </ul>
+                <div class="popup_btn_wrap2">
+<!--                     <button type="button">저장</button> -->
+                    <button type="button" class="tooltip-close" data-focus="adr_popup14-close" data-focus-next="adr_popup14">닫기</button>                      
+                </div>
+			</div>
+		</div>
+	</div>
+
+<!--// 중복전화번호 팝업 -->
+				<div class="popup_heading">
+					<p>엑셀 불러오기</p>
+					<button type="button" class="tooltip-close" id="closeBtn" data-focus="popup02-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+			   </div>
+				<div class="layer_in" style="padding: 25px 30px;">
+<!-- 					<div class="list_tab_wrap2"> -->
+						<!-- tab button -->
+<!-- 						<ul class="list_tab" id="tbTabl">  -->
+<!-- 							<li class="tab active" data-tabul="tableExcel"><button type="button" onclick="popupTab(this,'1'); fn_tabToggle('1');">엑셀입력</button></li> -->
+<!-- 							<li class="tab" data-tabul="tableClip"><button type="button" onclick="popupTab(this,'2'); fn_tabToggle('2');">붙여넣기</button></li> -->
+<!-- 							<li class="tab" data-tabul="tableSelf"><button type="button" onclick="popupTab(this,'3'); fn_tabToggle('3');">직접입력</button></li> -->
+<!-- 						</ul>// tab button -->
+<!-- 					</div> -->
+					<!-- 엑셀입력 -->
+					<div class="popCont current pop_more_cont" id="popCont_1">
+						<div class="titBox">
+							<p>- 주소록은 한 번에 최대 30만건까지 등록(EXCEL파일, 최대용량 20MB) 가능합니다. </p>
+							<p>- 엑셀 파일에 비밀번호 설정, 제한된 보기, 수식 등이 설정되어 있는 경우 업로드가 불가합니다.</p>
+							<p>- 구분선(|), 역슬래시(\, ₩), 큰따옴표(") 등 발송불가 특수문자는 저장되지 않습니다.</p>
+							<p>- 이름 200byte, [*1*]~[*4*] 200byte, 메모 250byte까지 입력 가능합니다.</p>
+							<p>- 주소록 등록이 어려우신 경우에는 <a href="<c:url value='/web/mjon/addragency/selectAddrAgencyList.do'/>" style="font-weight: bold; color: blue;">주소록 입력대행</a> 메뉴를 이용하실 수 있습니다. </p>
+						</div>
+						<div class="pop_more_wrap">
+								<button type="button" class="pop_more" onclick="popMore(this);">더보기<i></i></button>
+						</div>
+					</div><!--// 엑셀입력 -->
+
+					<!-- 공통 -->
+					<div>
+		                <table class="layer_tType1">
+		                    <caption>엑셀입력 표</caption>
+		                    <colgroup>
+		                        <col style="width: 95px">
+		                        <col style="width: auto">
+		                    </colgroup>
+		                    <tbody>
+		                        <tr>
+		                            <!-- <th>그룹 선택</th>
+		                            <td>
+		                                <label for="" class="label">그룹 선택</label>
+		                                <select id="addrGrpIdInfo" name="addrGrpIdInfo">
+		                                </select>
+	                                    <label for="" class="label">그룹명 입력</label>
+	                                    <input type="text" id="addrGrpNm" name="addrGrpNm" placeholder="새 그룹명을 입력해주세요." onfocus="this.placeholder=''" onblur="this.placeholder='새 그룹명을 입력해주세요.'"class="inputLight" style="width: 300px;">
+		                                <input type="file" id="excelFile" accept=".xls, .xlsx, .txt" style="display:none"/>
+		                                <button type="button" class="excel_btn2 btnType c3"><i class="uproad"></i>엑셀, TXT파일 업로드</button>
+		                            </td> -->
+		                            <td colspan="2" style="padding:20px 0;">
+		                            	<div class="file_upload_wrap" style="width:100%;display:flex;">
+						                	<div class="file_add upload_area">
+												<p><img src="/publish/images/content/file_add.png" alt="파일 붙여넣기">마우스로 엑셀, TXT파일을 여기에 끌어다 놓으세요</p>
+											</div>
+											<input type="file" id="excelFile" accept=".xls, .xlsx, .txt" style="display:none"/>
+					                        <button type="button" class="excel_btn2 btnType c3"><i class="uproad"></i>엑셀, TXT파일 업로드</button>
+						                </div>
+		                            </td>
+		                        </tr>
+		                    </tbody>
+		                </table>
+	                </div>
+	                
+					
+					<div class="excel_middle2">
+						<p>
+							총 <span class="c_e40000 fwBold" id="excelRowTotCnt">0</span>건 
+							/ 중복 <span class="c_002c9a fwBold" id="excelRowDupCnt">0</span>건
+							/ 오류 <span class="c_002c9a fwBold" id="excelRowErrorCnt">0</span>건
+							<button type="button" class="btn_list_detail" data-tooltip="adr_popup14"><img src="/publish/images/search.png"></button>
+						</p>
+<!-- 							&nbsp;  -->
+<!-- 							<button type="button" class="btnType btnType6" data-tooltip="addrMassDupli_layer" id="tableExcelDupliBtn">중복번호</button> -->
+<!-- 							&nbsp; -->
+<!-- 							<button type="button" class="btnType btnType6" data-tooltip="addrMassSaveDupli_layer" onclick="GetAddrMassSaveDupli()" id="btnAddrMassSaveDupli">중복번호</button> -->
+						</p>
+<!-- 						<button type="button" class="btnType btnType6 addCallToF">번호추가</button> -->
+					</div>
+					
+					
+					
+
+					<div class="adr_excel" style="margin-top: 13px; overflow-x:auto;">
+<!--                     <div class="adr_excel" style="margin-top: 13px;"> -->
+                        <!-- thead -->
+                        <div class="adr_hd select_adr_hd" data-group="tableExcel">
+                            <div style="width: 100px;"></div>
+                            <div style="width: 140px;">
+                            	<label for="" class="label"></label>
+								<select class="field-selector">
+									<option value="">선택하기</option>
+									<option value="addrNm">이름</option>
+									<option value="addrPhoneNo">휴대폰</option>
+									<option value="addrInfo1">[*1*]</option>
+									<option value="addrInfo2">[*2*]</option>
+									<option value="addrInfo3">[*3*]</option>
+									<option value="addrInfo4">[*4*]</option>
+<!-- 									<option value="addrComment">메모</option> -->
+								</select>
+                            </div>
+                            <div style="width: 140px;">
+                                <label for="" class="label"></label>
+								<select class="field-selector">
+									<option value="">선택하기</option>
+									<option value="addrNm">이름</option>
+									<option value="addrPhoneNo">휴대폰</option>
+									<option value="addrInfo1">[*1*]</option>
+									<option value="addrInfo2">[*2*]</option>
+									<option value="addrInfo3">[*3*]</option>
+									<option value="addrInfo4">[*4*]</option>
+<!-- 									<option value="addrComment">메모</option> -->
+								</select>
+                            </div>
+                            <div style="width: 140px;">
+                                <label for="" class="label"></label>
+								<select class="field-selector">
+									<option value="">선택하기</option>
+									<option value="addrNm">이름</option>
+									<option value="addrPhoneNo">휴대폰</option>
+									<option value="addrInfo1">[*1*]</option>
+									<option value="addrInfo2">[*2*]</option>
+									<option value="addrInfo3">[*3*]</option>
+									<option value="addrInfo4">[*4*]</option>
+<!-- 									<option value="addrComment">메모</option> -->
+								</select>
+                            </div>
+                            <div style="width: 140px;">
+                                <label for="" class="label"></label>
+								<select class="field-selector">
+									<option value="">선택하기</option>
+									<option value="addrNm">이름</option>
+									<option value="addrPhoneNo">휴대폰</option>
+									<option value="addrInfo1">[*1*]</option>
+									<option value="addrInfo2">[*2*]</option>
+									<option value="addrInfo3">[*3*]</option>
+									<option value="addrInfo4">[*4*]</option>
+<!-- 									<option value="addrComment">메모</option> -->
+								</select>
+                            </div>
+                            <div style="width: 140px;">
+                                <label for="" class="label"></label>
+								<select class="field-selector">
+									<option value="">선택하기</option>
+									<option value="addrNm">이름</option>
+									<option value="addrPhoneNo">휴대폰</option>
+									<option value="addrInfo1">[*1*]</option>
+									<option value="addrInfo2">[*2*]</option>
+									<option value="addrInfo3">[*3*]</option>
+									<option value="addrInfo4">[*4*]</option>
+<!-- 									<option value="addrComment">메모</option> -->
+								</select>
+                            </div>
+                            <div style="width: 140px;">
+                                <label for="" class="label"></label>
+								<select class="field-selector">
+									<option value="">선택하기</option>
+									<option value="addrNm">이름</option>
+									<option value="addrPhoneNo">휴대폰</option>
+									<option value="addrInfo1">[*1*]</option>
+									<option value="addrInfo2">[*2*]</option>
+									<option value="addrInfo3">[*3*]</option>
+									<option value="addrInfo4">[*4*]</option>
+<!-- 									<option value="addrComment">메모</option> -->
+								</select>
+                            </div>
+                            <!-- <div style="width: 125px;">
+                                <label for="" class="label"></label>
+								<select class="field-selector">
+									<option value="">선택하기</option>
+									<option value="addrNm">이름</option>
+									<option value="addrPhoneNo">휴대폰</option>
+									<option value="addrInfo1">[*1*]</option>
+									<option value="addrInfo2">[*2*]</option>
+									<option value="addrInfo3">[*3*]</option>
+									<option value="addrInfo4">[*4*]</option>
+									<option value="addrComment">메모</option>
+								</select>
+                            </div> -->
+                        </div>
+                     </div>					
+					
+					<div class="drag_drop_wrap callList_includ_box" id="tabulator_excel">
+<!-- 						<img src="/publish/images/content/excel.jpg" style="width: 100%;"> -->
+					</div>
+					<div class="excel_middle">
+						<div class="select_btnWrap clearfix">
+							<div>
+								<button type="button" id="allDel"><i class="remove_img"></i>전체삭제</button>
+								<button type="button" id="in_select_del"><i class="remove_img"></i>선택삭제</button>
+							</div>
+	
+						</div>
+					</div><!--// 공통 -->     
+	
+					<!-- 붙여놓기 설명 -->
+<!-- 	                <div class="req_area"> -->
+<!-- 	                  	<div class="text_box"> -->
+<!-- 	                    	- 휴대폰 번호가 입력된 txt 파일을 열어 복사(Ctrl+c) + 붙여넣기(Ctrl+v)로도 입력하실 수 있습니다.<br> -->
+<!-- 							- 휴대폰 번호는 필수입력 항목입니다.<br> -->
+<!-- 							- 이름,휴대폰 번호,[*1*],[*2*],[*3*],[*4*],메모 순서대로 입력해주세요.(예 : 010-1234-5678,홍길동,변수1…메모)<br> -->
+<!-- 							- 이름은 24byte, [*1*]~[*4*] 40byte, 메모는 250byte까지 입력 가능합니다.<br> -->
+<!-- 							- '오류 검사'를 통해 등록된 데이터에 전화번호 입력 오류를 확인하실 수 있습니다. -->
+<!-- 						</div> -->
+<!-- 	                </div> -->
+					<div class="popup_btn_wrap2" style="margin: 0 auto 30px auto;">
+						<button type="button" id="btnAddrMassReg">추가</button>
+						<button type="button" id="btnAddrMassClose" class="tooltip-close" data-focus="adr_popup01-close"  data-focus-next="popup02">닫기</button>                      
+					</div>
+				
 				</div>
(No newline at end of file)
Add a comment
List