이호영 이호영 2024-11-27
문자전송개선 테스트
@f44acfe2c4e96b645ed3324c7d87d75aa787fe80
pom.xml
--- pom.xml
+++ pom.xml
@@ -111,6 +111,7 @@
 			<groupId>egovframework.rte</groupId>
 			<artifactId>egovframework.rte.fdl.idgnr</artifactId>
 			<version>${egovframework.rte.version}</version>
+			<!--<version>4.2.0</version>-->
 		</dependency>
 		<dependency>
 			<groupId>egovframework.rte</groupId>
 
src/main/java/egovframework/com/idgen/CustomIdGnrService.java (added)
+++ src/main/java/egovframework/com/idgen/CustomIdGnrService.java
@@ -0,0 +1,10 @@
+package egovframework.com.idgen;
+
+import java.util.List;
+
+import egovframework.rte.fdl.cmmn.exception.FdlException;
+import egovframework.rte.fdl.idgnr.EgovIdGnrService;
+
+public interface CustomIdGnrService extends EgovIdGnrService {
+    List<String> getNextStringId(int count) throws FdlException;
+}
 
src/main/java/egovframework/com/idgen/impl/CustomTableIdGnrServiceImpl.java (added)
+++ src/main/java/egovframework/com/idgen/impl/CustomTableIdGnrServiceImpl.java
@@ -0,0 +1,196 @@
+package egovframework.com.idgen.impl;
+
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.PreparedStatement; // java.sql.PreparedStatement로 임포트
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.springframework.jdbc.datasource.DataSourceUtils;
+
+import egovframework.com.idgen.CustomIdGnrService;
+import egovframework.rte.fdl.cmmn.exception.FdlException;
+import egovframework.rte.fdl.idgnr.EgovIdGnrStrategy;
+
+public class CustomTableIdGnrServiceImpl implements CustomIdGnrService {
+
+	private long allocatedId;
+	private long allocatedMaxId;
+	private int blockSize = 10; // 기본값, 필요에 따라 변경
+	private DataSource dataSource;
+	private String table; // 실제 테이블 이름 (예: IDS)
+	private String tableName; // TABLE_NAME 컬럼에서 사용할 값 (예: MSG_ID)
+	private String columnName = "NEXT_ID"; // NEXT_ID 컬럼명
+	private String prefix;
+	private String fillChar = "0"; // 채울 문자 (예: 0)
+	private int cipers = 14; // 자리수 (예: 14)
+	private boolean applyYear;
+	private boolean useBigDecimals = false;
+
+	@Override
+	public synchronized List<String> getNextStringId(int count) throws FdlException {
+		List<String> idList = new ArrayList<>(count);
+		try {
+			for (int i = 0; i < count; i++) {
+				if (allocatedId >= allocatedMaxId) {
+					allocateIdBlock(count);
+				}
+				long id = allocatedId++;
+				idList.add(createStringId(id));
+			}
+		} catch (Exception e) {
+			throw new FdlException("ID Generation Error", e);
+		}
+		return idList;
+	}
+
+	private void allocateIdBlock(int requiredCount) throws SQLException, FdlException {
+		Connection conn = DataSourceUtils.getConnection(dataSource);
+		try {
+			conn.setAutoCommit(false);
+
+			int newBlockSize = Math.max(this.blockSize, requiredCount);
+
+			// SELECT 쿼리 수정
+			String query = "SELECT " + columnName + " FROM " + table + " WHERE TABLE_NAME = ? FOR UPDATE";
+			try (PreparedStatement stmt = conn.prepareStatement(query)) {
+				stmt.setString(1, tableName);
+				try (ResultSet rs = stmt.executeQuery()) {
+					long oldId = 0;
+					if (rs.next()) {
+						oldId = rs.getLong(1);
+					} else {
+						throw new FdlException(
+								"ID Generation Error: No record found in " + table + " for TABLE_NAME = " + tableName);
+					}
+
+					long newId = oldId + newBlockSize;
+
+					// UPDATE 쿼리 수정
+					String update = "UPDATE " + table + " SET " + columnName + " = ? WHERE TABLE_NAME = ? AND "
+							+ columnName + " = ?";
+					try (PreparedStatement updateStmt = conn.prepareStatement(update)) {
+						updateStmt.setLong(1, newId);
+						updateStmt.setString(2, tableName);
+						updateStmt.setLong(3, oldId);
+						int row = updateStmt.executeUpdate();
+
+						if (row == 0) {
+							throw new FdlException(
+									"ID Generation Error: Failed to update ID. Possible concurrent modification.");
+						}
+					}
+
+					conn.commit();
+
+					allocatedId = oldId;
+					allocatedMaxId = newId;
+				}
+			} catch (SQLException e) {
+				conn.rollback();
+				throw e;
+			}
+		} catch (SQLException e) {
+			throw new FdlException("ID Generation Error", e);
+		} finally {
+			DataSourceUtils.releaseConnection(conn, dataSource);
+		}
+	}
+
+	private String createStringId(long id) {
+		StringBuilder sb = new StringBuilder();
+		if (prefix != null) {
+			sb.append(prefix);
+		}
+		if (applyYear) {
+			sb.append(new SimpleDateFormat("yyyy").format(new Date()));
+		}
+		String idStr = String.format("%0" + cipers + "d", id);
+		sb.append(idStr);
+		return sb.toString();
+	}
+
+	// 인터페이스의 다른 메서드 구현 (필요에 따라 UnsupportedOperationException 또는 직접 구현)
+	@Override
+	public BigDecimal getNextBigDecimalId() throws FdlException {
+		throw new UnsupportedOperationException("getNextBigDecimalId is not supported");
+	}
+
+	@Override
+	public long getNextLongId() throws FdlException {
+		throw new UnsupportedOperationException("getNextLongId is not supported");
+	}
+
+	@Override
+	public int getNextIntegerId() throws FdlException {
+		throw new UnsupportedOperationException("getNextIntegerId is not supported");
+	}
+
+	@Override
+	public short getNextShortId() throws FdlException {
+		throw new UnsupportedOperationException("getNextShortId is not supported");
+	}
+
+	@Override
+	public byte getNextByteId() throws FdlException {
+		throw new UnsupportedOperationException("getNextByteId is not supported");
+	}
+
+	@Override
+	public String getNextStringId() throws FdlException {
+		throw new UnsupportedOperationException("getNextStringId is not supported");
+	}
+
+	@Override
+	public String getNextStringId(String strategyId) throws FdlException {
+		throw new UnsupportedOperationException("getNextStringId(String strategyId) is not supported");
+	}
+
+	@Override
+	public String getNextStringId(EgovIdGnrStrategy strategy) throws FdlException {
+		throw new UnsupportedOperationException("getNextStringId(EgovIdGnrStrategy strategy) is not supported");
+	}
+
+	// 필요한 setter 메서드들 추가
+	public void setDataSource(DataSource dataSource) {
+		this.dataSource = dataSource;
+	}
+
+	public void setTable(String table) {
+		this.table = table;
+	}
+
+	public void setTableName(String tableName) {
+		this.tableName = tableName;
+	}
+
+	public void setColumnName(String columnName) {
+		this.columnName = columnName;
+	}
+
+	public void setPrefix(String prefix) {
+		this.prefix = prefix;
+	}
+
+	public void setFillChar(String fillChar) {
+		this.fillChar = fillChar;
+	}
+
+	public void setCipers(int cipers) {
+		this.cipers = cipers;
+	}
+
+	public void setApplyYear(boolean applyYear) {
+		this.applyYear = applyYear;
+	}
+
+	public void setBlockSize(int blockSize) {
+		this.blockSize = blockSize;
+	}
+}
 
src/main/java/itn/com/cmm/OptimalMsgResultDTO.java (added)
+++ src/main/java/itn/com/cmm/OptimalMsgResultDTO.java
@@ -0,0 +1,37 @@
+package itn.com.cmm;
+
+import java.util.List;
+
+import itn.let.mjo.event.service.MjonEventVO;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * 
+ * @author 		: 이호영
+ * @fileName 	: OptimalMsgResultDTO.java 
+ * @date 		: 2024.11.26
+ * @description : 이벤트 vo 생성 및 종료 vo 생성
+ * =========================================================== 
+ * DATE          AUTHOR   NOTE 
+ * ----------------------------------------------------------- *
+ * 2024.11.26    이호영          최초 생성
+ * 
+ * 
+ * 
+ */
+@Getter
+@Setter
+@ToString
+@Builder
+public class OptimalMsgResultDTO{
+
+	private List<MjonMsgSendVO> optimalMsgList;
+	private MjonEventVO eventInfo;
+
+
+	
+	
+}
src/main/java/itn/com/cmm/util/MJUtil.java
--- src/main/java/itn/com/cmm/util/MJUtil.java
+++ src/main/java/itn/com/cmm/util/MJUtil.java
@@ -144,7 +144,6 @@
     	date = date.replaceAll("-", "/");//날짜에 하이픈(-)을 슬러쉬(/)로 변환
     	
     	SimpleDateFormat sdformat = new SimpleDateFormat("yyyy/MM/dd");
-    	System.out.println(getRealTime());
     	
         Date nowDate = sdformat.parse(getRealTime());
         Date endDate = sdformat.parse(date);
src/main/java/itn/com/cmm/util/MsgSendUtils.java
--- src/main/java/itn/com/cmm/util/MsgSendUtils.java
+++ src/main/java/itn/com/cmm/util/MsgSendUtils.java
@@ -1,10 +1,9 @@
 package itn.com.cmm.util;
 
 import java.io.UnsupportedEncodingException;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.HashMap;
@@ -18,14 +17,15 @@
 import java.util.stream.Stream;
 
 import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 
 import itn.com.cmm.MjonMsgSendVO;
+import itn.com.cmm.OptimalMsgResultDTO;
 import itn.let.mail.service.StatusResponse;
+import itn.let.mjo.event.service.MjonEventVO;
 import itn.let.mjo.msg.service.MjonMsgVO;
+import itn.let.mjo.msgagent.service.MjonMsgAgentStsVO;
 import itn.let.mjo.spammsg.web.ComGetSpamStringParser;
-import itn.let.module.base.PriceAndPoint;
 import itn.let.sym.site.service.JoinSettingVO;
 import itn.let.uss.umt.service.MberManageVO;
 import lombok.extern.slf4j.Slf4j;
@@ -47,6 +47,12 @@
 @Slf4j
 public final class MsgSendUtils {
 
+	// 단문 메세지 타입
+	public static final String SHORT_MSG_TYPE = "4";
+	// 장문 메세지 타입
+	public static final String LONG_MSG_TYPE = "6";
+	// 이벤트 최저 잔액
+	public static final double MIN_EVENT_REMAIN_CASH = 7.5; // 이벤트 최소 잔액
 	
 	/** 
 	 * @methodName	: getSmsTxtBytes 
@@ -81,28 +87,30 @@
 	 */
 	public static String getMsgTypeWithByteValidation(MjonMsgSendVO sendVO, String p_smsTxt) throws UnsupportedEncodingException {
 		
-		
+
+		//	// 내문자저장함에 저장 후 문자를 발송하는 경우 문자 타입이 숫자가 아닌 문자로 넘어와서 변경 처리함
+		//	if ("P".equals(msgType) || "L".equals(msgType)) {
+		//	    msgType = "6";
+		//	} else if ("S".equals(msgType)) {
+		//	    msgType = "4";
+		//	}
+			
 		int smsTxtByte = getSmsTxtBytes(p_smsTxt);
-		String msgType = "4";
+		String msgType = SHORT_MSG_TYPE;
 	
-	//	// 내문자저장함에 저장 후 문자를 발송하는 경우 문자 타입이 숫자가 아닌 문자로 넘어와서 변경 처리함
-	//	if ("P".equals(msgType) || "L".equals(msgType)) {
-	//	    msgType = "6";
-	//	} else if ("S".equals(msgType)) {
-	//	    msgType = "4";
-	//	}
-	
-		// 그림 이미지가 첨부된 경우 장문으로 설정
-		if (StringUtils.isNotEmpty(sendVO.getFilePath1())) {
-			msgType = "6";
-		} else if (smsTxtByte > 2000) {
-			// 2000 Byte를 초과할 경우 에러 처리 (이 부분은 호출부에서 검사하도록 유지할 수도 있음)
-			return "INVALID";  // 이 값은 호출부에서 에러 처리를 하도록 활용할 수 있습니다.
-		} else if (smsTxtByte > 90) {
-			// 90Byte 초과 시 장문으로 설정
-			msgType = "6";
+		// 1. 2000 Byte 초과는 에러 처리
+		if (smsTxtByte > 2000) {
+			return "INVALID";
 		}
 
+		// 2. 첨부파일 여부 확인 (첨부파일이 있으면 장문으로 설정)
+		if (StringUtils.isNotEmpty(sendVO.getFilePath1())) {
+			msgType = LONG_MSG_TYPE;
+		}
+		// 3. 문자 길이에 따라 메시지 타입 설정 (90 Byte 초과는 장문)
+		else if (smsTxtByte > 90) {
+			msgType = LONG_MSG_TYPE;
+		}
 		return msgType;
 	}
 
@@ -129,9 +137,9 @@
 		float price = 0;
 		String msgType = mjonMsgVO.getMsgType();
 	
-		if ("4".equals(msgType)) {
+		if (SHORT_MSG_TYPE.equals(msgType)) {
 			price = shortPrice;
-		} else if ("6".equals(msgType)) {
+		} else if (LONG_MSG_TYPE.equals(msgType)) {
 			// 파일 첨부 여부에 따라 장문 단가 설정
 			if (mjonMsgVO.getFileName3() != null) {
 				price = picture3Price;
@@ -181,19 +189,18 @@
 	 * @param mjonMsgVO
 	 * @param lists
 	 * @param statusResponse
+	 * @param agentSendCounts 
+	 * @param sendRateList 
 	 * @return
 	 * @throws Exception 
 	 */
-	public static Boolean populateSendLists(MjonMsgVO mjonMsgVO, List<MjonMsgSendVO> mjonMsgSendListVO, StatusResponse statusResponse, List<String> resultSpamTxt) throws Exception{
+	public static Boolean populateSendLists(MjonMsgVO mjonMsgVO, List<MjonMsgSendVO> mjonMsgSendListVO
+			, StatusResponse statusResponse, List<String> resultSpamTxt
+			, Map<String, Integer> agentSendCounts, List<MjonMsgVO> sendRateList, String nextMsgGroupId) throws Exception{
 		
 		log.info(" :: populateSendLists :: ");
 		
 		
-		log.info(" + 예약여부 [{}]", mjonMsgVO.getReserveYn()); // 예약 여부
-		log.info(" + 시간 [{}]", mjonMsgVO.getReqDate()); // 시간
-		log.info(" + 분할체크  [{}]", mjonMsgVO.getDivideChk()); // 분할체크 
-		log.info(" + 간격(분) [{}]", mjonMsgVO.getDivideTime()); // 간격
-		log.info(" + 몇 건씩 [{}]", mjonMsgVO.getDivideCnt()); // 몇 건식
 
 
 		// 예약 시간 기본값 설정
@@ -230,12 +237,17 @@
 		boolean hasPerformedSpamCheck = false; // 치환 문자가 없는 경우, 스팸 체크가 한 번만 수행되도록 제어
 		boolean hasPerformedMsgType = false; // 치환 문자가 없는 경우, 스팸 체크가 한 번만 수행되도록 제어
 		
+		String msgKind = mjonMsgVO.getMsgKind();
 		String smsTxtTemp = mjonMsgVO.getSmsTxt();
 		Boolean replaceYN = getReplaceYN(smsTxtTemp);
 		
 		String msgTypeResult = null;
 		
 		for (MjonMsgSendVO sendVO : mjonMsgSendListVO) {
+			
+			sendVO.setCallFrom(mjonMsgVO.getCallFrom());
+			sendVO.setCallTo(sendVO.getPhone());
+			sendVO.setUserId(mjonMsgVO.getUserId());
 
 			String smsTxt = smsTxtTemp;
 			// 치환 문자면
@@ -255,46 +267,51 @@
 				}
 			}
 
-			String smsSpamChkTxt = mjonMsgVO.getSmsTxt().replaceAll(String.valueOf((char) 13), "");
-			// 스팸문자 체크
-			// 치환문자가 아닐 경우
-			if (!replaceYN && !hasPerformedSpamCheck) {
-				checkSpamAndSetStatus(mjonMsgVO, smsSpamChkTxt, resultSpamTxt);
-				hasPerformedSpamCheck = true; // 스팸 체크가 한 번만 수행되도록 설정
-			// 치환 문자가 있는 경우에는 spamChkSize 카운트까지만 수행
-			} else if (replaceYN && sampleCounter < spamChkSize && !"Y".equals(mjonMsgVO.getSpamStatus())) {
-				checkSpamAndSetStatus(mjonMsgVO, smsSpamChkTxt, resultSpamTxt);
-				sampleCounter++;
+			String smsSpamChkTxt = smsTxt.replaceAll(String.valueOf((char) 13), "");
+
+			// == 치환 여부에 따라 처리 로직 분기 == 
+			// 치환 문자가 아닌 경우
+			if (!replaceYN) {
+				// 스팸 체크와 메시지 타입 체크 각각 한 번만 수행
+				if (!hasPerformedSpamCheck) {
+					checkSpamAndSetStatus(mjonMsgVO, smsSpamChkTxt, resultSpamTxt);
+					hasPerformedSpamCheck = true;
+				}
+				if (!hasPerformedMsgType) {
+					msgTypeResult = getMsgTypeWithByteValidation(sendVO, smsTxt);
+					if ("INVALID".equals(msgTypeResult)) {
+						statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다.");
+						return false;
+					}
+					hasPerformedMsgType = true;
+				}
+			} 
+			else  
+			{// 치환 문자인 경우
+				// 스팸 체크는 `spamChkSize`만큼 반복 수행
+				if (sampleCounter < spamChkSize && !"Y".equals(mjonMsgVO.getSpamStatus())) {
+					checkSpamAndSetStatus(mjonMsgVO, smsSpamChkTxt, resultSpamTxt);
+					sampleCounter++;
+				}
+
+				// 메시지 타입 체크는 매번 수행
+				msgTypeResult = getMsgTypeWithByteValidation(sendVO, smsTxt);
+				if ("INVALID".equals(msgTypeResult)) {
+					statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다.");
+					return false;
+				}
 			}
+			
+			
 			sendVO.setSmsTxt(smsTxt);
-			
-			// 이미지 셋팅
-			setImagePathsForMsgSendVO(mjonMsgVO, sendVO);
-			
-
-			// msgType 셋팅 및 문자열 체크
-			if (!replaceYN && !hasPerformedMsgType) {
-				log.info(" 치환 X ");
-				// byte 체크와 msgType 구하기
-				msgTypeResult = getMsgTypeWithByteValidation(sendVO, smsTxt);
-				if ("INVALID".equals(msgTypeResult)) {
-					statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다.");
-					return false;
-				}
-				hasPerformedMsgType = true; // 스팸 체크가 한 번만 수행되도록 설정
-			}else if(replaceYN){
-				log.info(" 치환 O ");
-				// byte 체크와 msgType 구하기
-				msgTypeResult = getMsgTypeWithByteValidation(sendVO, smsTxt);
-				if ("INVALID".equals(msgTypeResult)) {
-					statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다.");
-					return false;
-				}
-			}
-
 			sendVO.setMsgType(msgTypeResult);
 			
+			// 제목 셋팅
 			
+			
+
+			// 이미지 셋팅
+			setImagePathsForMsgSendVO(mjonMsgVO, sendVO);
 			
 			
 			// 예약 여부 확인
@@ -315,6 +332,16 @@
 			}
 			
 			
+			// 전송사 코드 셋팅
+			String agentCode = "00".equals(mjonMsgVO.getAgentCode())
+					? MsgSendUtils.assignAgentBasedOnCount(agentSendCounts, sendRateList)
+					: mjonMsgVO.getAgentCode();
+				sendVO.setAgentCode(agentCode);
+			
+			sendVO.setMsgGroupId(nextMsgGroupId);
+			
+				
+				
 			
 			
 			
@@ -324,6 +351,14 @@
 
 	}
 	
+	/** 
+	 * @methodName	: setImagePathsForMsgSendVO 
+	 * @author		: 이호영
+	 * @date		: 2024.11.26 
+	 * @description	: vo에 이미지 셋팅
+	 * @param mjonMsgVO
+	 * @param sendVO 
+	 */
 	private static void setImagePathsForMsgSendVO(MjonMsgVO mjonMsgVO, MjonMsgSendVO sendVO) {
 		int fileCount = Integer.parseInt(mjonMsgVO.getFileCnt());
 
@@ -493,6 +528,7 @@
 		float picture2Price = MsgSendUtils.getValidPrice(mberManageVO.getPicture2Price(), sysJoinSetVO.getPicture2Price());
 		float picture3Price = MsgSendUtils.getValidPrice(mberManageVO.getPicture3Price(), sysJoinSetVO.getPicture3Price());
 
+		
 		// 각 메시지 타입에 따라 사용자 단가 설정 및 총 단가 계산
 		float totalPrice = 0.0f;
 
@@ -501,18 +537,17 @@
 			String eachPrice;
 	
 			switch (msgType) {
-				case "4": // 단문 메시지 타입
+				case SHORT_MSG_TYPE: // 단문 메시지 타입
 					eachPrice = Float.toString(shortPrice);
 					break;
 		
-				case "6": // 장문 또는 이미지 메시지 타입
+				case LONG_MSG_TYPE: // 장문 또는 이미지 메시지 타입
 					eachPrice = getPicturePrice(sendVO, longPrice, picturePrice, picture2Price, picture3Price);
 					break;
 		
 				default:
 					// 기본값이 필요하다면 추가 가능
-					eachPrice = "0";
-					break;
+					throw new IllegalArgumentException("setPriceforVO :: type Exception ");
 			}
 	
 			sendVO.setEachPrice(eachPrice);
@@ -531,46 +566,271 @@
 	 * 이미지 파일 경로를 기준으로 적절한 가격을 반환하는 헬퍼 메소드.
 	 */
 	private static String getPicturePrice(MjonMsgSendVO sendVO, float longPrice, float picturePrice, float picture2Price, float picture3Price) {
-		if (StringUtils.isNotEmpty(sendVO.getFilePath3())) {
+		
+		switch (sendVO.getFilePath3() != null ? "FILE3" :
+			sendVO.getFilePath2() != null ? "FILE2" :
+			sendVO.getFilePath1() != null ? "FILE1" : "NONE") {
+		case "FILE3":
 			return Float.toString(picture3Price);
-		} else if (StringUtils.isNotEmpty(sendVO.getFilePath2())) {
+		case "FILE2":
 			return Float.toString(picture2Price);
-		} else if (StringUtils.isNotEmpty(sendVO.getFilePath1())) {
+		case "FILE1":
 			return Float.toString(picturePrice);
-		} else {
+		default:
 			return Float.toString(longPrice);
+		}
+		
+	}
+	
+	/** 
+	 * @methodName	: getOptimalMsgList 
+	 * @author		: 이호영
+	 * @date		: 2024.11.26 
+	 * @description	: 주어진 이벤트 정보와 메시지 리스트를 기반으로, 잔액 내에서 최적의 메시지 리스트를 생성하고 결과를 반환합니다.
+	 *                만약 잔액이 부족하거나 이벤트가 종료된 상태라면, 이벤트 종료 정보를 포함하여 반환합니다.
+	 * 
+	 * @param eventMberInfo  이벤트 정보 객체 (MjonEventVO). 이벤트 상태 및 잔액 정보를 포함.
+	 * @param mjonMsgSendVOList  최적화를 수행할 메시지 리스트 (List<MjonMsgSendVO>).
+	 * @return OptimalMsgResultDTO  최적 메시지 리스트와 이벤트 상태 정보를 포함한 DTO 객체.
+	 * @throws Exception  메시지 단가 계산 오류 또는 기타 처리 중 발생한 예외를 상위로 전달.
+	 */
+	public static OptimalMsgResultDTO getOptimalMsgList(
+			MjonEventVO eventMberInfo,
+			List<MjonMsgSendVO> mjonMsgSendVOList) throws Exception {
+
+		// 최적의 메시지 리스트를 담을 리스트
+		List<MjonMsgSendVO> optimalList = new ArrayList<>();
+
+		// 이벤트 정보가 없는 경우
+		if (eventMberInfo == null) {
+			return OptimalMsgResultDTO.builder()
+					.optimalMsgList(optimalList)
+					.eventInfo(null)
+					.build();
+		}
+
+		// 이벤트 잔액과 종료 조건 확인
+		double targetCash = eventMberInfo.getEventRemainCash();
+		String eventEndDate = eventMberInfo.getEventEndDate();
+
+		if (isEventExpired(targetCash, eventEndDate)) {
+			MjonEventVO returnEventMberInfo = terminateEvent(eventMberInfo, targetCash);
+			return OptimalMsgResultDTO.builder()
+					.optimalMsgList(optimalList)
+					.eventInfo(returnEventMberInfo)
+					.build();
+		}
+
+		// 단가 설정
+		float shortPrice = Float.parseFloat(eventMberInfo.getEventShortPrice());
+		float longPrice = Float.parseFloat(eventMberInfo.getEventLongPrice());
+		float picturePrice = Float.parseFloat(eventMberInfo.getEventPicturePrice());
+		float picture2Price = Float.parseFloat(eventMberInfo.getEventPicture2Price());
+		float picture3Price = Float.parseFloat(eventMberInfo.getEventPicture3Price());
+
+		// 최적의 메시지 리스트 생성
+		double sum = 0.0;
+		Iterator<MjonMsgSendVO> iterator = mjonMsgSendVOList.iterator();
+
+		while (iterator.hasNext()) {
+			MjonMsgSendVO msg = iterator.next();
+
+			// 단가 계산 및 예외 처리
+			String eachPrice = getEachPriceOrThrow(msg, shortPrice, longPrice, picturePrice, picture2Price, picture3Price);
+
+			float floatEachPrice = Float.parseFloat(eachPrice);
+
+			// 최적의 메시지 리스트에 추가
+			if (sum + floatEachPrice <= targetCash) {
+				sum += floatEachPrice;
+				msg.setEachPrice(eachPrice);
+				optimalList.add(msg);
+				iterator.remove();
+			} else {
+				break; // 예산 초과로 추가 불가능
+			}
+		}
+
+		// 잔액 부족 시 이벤트 종료 처리하는 VO 생성
+		double remainAmt = targetCash - sum; 
+		log.info("remainAmt :: [{}]", remainAmt);
+		if (remainAmt < MIN_EVENT_REMAIN_CASH) {
+			MjonEventVO returnEventMberInfo = terminateEvent(eventMberInfo, remainAmt);
+			return OptimalMsgResultDTO.builder()
+					.optimalMsgList(optimalList)
+					.eventInfo(returnEventMberInfo)
+					.build();
+		}
+
+		// 결과 반환
+		return OptimalMsgResultDTO.builder()
+				.optimalMsgList(optimalList)
+				.eventInfo(null)
+				.build();
+	}
+	
+	/** 
+	 * @methodName	: getEachPriceOrThrow 
+	 * @author		: 이호영
+	 * @date		: 2024.11.26 
+	 * @description	: 
+	 * @param msg
+	 * @param shortPrice
+	 * @param longPrice
+	 * @param picturePrice
+	 * @param picture2Price
+	 * @param picture3Price
+	 * @return 
+	 */
+	private static String getEachPriceOrThrow(
+			MjonMsgSendVO msg,
+			float shortPrice,
+			float longPrice,
+			float picturePrice,
+			float picture2Price,
+			float picture3Price) {
+
+		switch (msg.getMsgType()) {
+			case SHORT_MSG_TYPE:
+				return Float.toString(shortPrice);
+			case LONG_MSG_TYPE:
+				return getPicturePrice(msg, longPrice, picturePrice, picture2Price, picture3Price);
+			default:
+				throw new IllegalArgumentException("Unsupported message type: " + msg.getMsgType());
 		}
 	}
 
-	public static List<MjonMsgSendVO> getOptimalMsgList(double totalPrice, double eventRemainCash,
-			List<MjonMsgSendVO> mjonMsgSendVOList) {
-		
-		double targetCash = eventRemainCash;
-		
-		// 최적의 리스트를 구성하기 위한 빈 리스트 생성
-		List<MjonMsgSendVO> optimalList = new ArrayList<>();
-		double sum = 0.0;
+	private static boolean isEventExpired(double targetCash, String eventEndDate) throws Exception {
+		return !MJUtil.getCompareDate(eventEndDate) || targetCash < MIN_EVENT_REMAIN_CASH;
+	}
 	
-		// 원본 리스트의 요소를 순회하며 조건에 맞는 항목을 최적 리스트에 추가
-		Iterator<MjonMsgSendVO> iterator = mjonMsgSendVOList.iterator();
-		while (iterator.hasNext()) {
-			MjonMsgSendVO msg = iterator.next();
-			double eachPrice = Double.parseDouble(msg.getEachPrice());
-			
-			// sum + eachPrice가 정확히 targetCash와 같거나 작을 때만 추가하고 원본에서 제거
-			if (sum + eachPrice <= targetCash) {
-				sum += eachPrice;
-				optimalList.add(msg);
-				iterator.remove();  // 원본 리스트에서 해당 요소 제거
-			} else {
-				break;  // 초과하지 않도록, 더 이상 추가할 수 없는 경우 종료
+	/** 
+	 * @methodName	: terminateEvent 
+	 * @author		: 이호영
+	 * @date		: 2024.11.26 
+	 * @description	: 이벤트 종료 VO 생성
+	 * @param eventMberInfo
+	 * @param targetCash
+	 * @return 
+	 */
+	private static MjonEventVO terminateEvent(MjonEventVO eventMberInfo, double targetCash) {
+		// TODO Auto-generated method stub
+
+		// 이벤트 상태를 종료로 변경
+		MjonEventVO returnEventMberInfo = new MjonEventVO();
+		returnEventMberInfo.setEventInfoId(eventMberInfo.getEventInfoId());
+		returnEventMberInfo.setEventStatus("E");
+		returnEventMberInfo.setEventRemainCash(targetCash);
+		returnEventMberInfo.setEventMemo("발송 최소 금액("+ MIN_EVENT_REMAIN_CASH +") 부족 혹은 이벤트 종료일 초과되어 이벤트 종료 시킴");
+		return returnEventMberInfo;
+	}
+
+	/** 
+	 * @methodName	: handleHotlineAgentRate 
+	 * @author		: 이호영
+	 * @date		: 2024.11.26 
+	 * @description	: 전용 전송사 비율이 0이면 대표 전송사로 수정
+	 * @param userInfo
+	 * @param tmp
+	 * @param hotlineAgentCode
+	 * @return 
+	 */
+	public static String handleHotlineAgentRate(MberManageVO userInfo, MjonMsgAgentStsVO tmp, String hotlineAgentCode) {
+		String sendRate = tmp.getSendRate();
+		String repAgent = tmp.getRepAgent();
+		String useYn = tmp.getUseYn();
+
+		// 블라인드 코드가 'N'인 경우 전송 비율 및 사용 가능 여부 확인
+		if ("N".equals(userInfo.getBlineCode())) {
+			// 전송 비율이 0이거나 사용 불가인 경우 대표 전송사로 변경
+			if ("0".equals(sendRate) || "N".equals(useYn)) {
+				hotlineAgentCode = repAgent;
 			}
 		}
-		log.info(" + targetCash  :: [{}]", targetCash);
-		log.info(" + sum  :: [{}]", sum);
-	
-		// 최적 리스트 반환
-		return optimalList;
-		
+
+		return hotlineAgentCode; // 변경된 hotlineAgentCode 반환
 	}
+
+
+	/** 
+	 * @methodName	: initializeAgentSendCounts 
+	 * @author		: 이호영
+	 * @date		: 2024.11.26 
+	 * @description	: 전송사별 코드로 map 초기화 하기
+	 * @return 
+	 */
+	public static Map<String, Integer> initializeAgentSendCounts() {
+		Map<String, Integer> agentSendCounts = new HashMap<>();
+		agentSendCounts.put("01", 0); // 아이하트
+		agentSendCounts.put("02", 0); // 현대 퓨쳐넷
+		agentSendCounts.put("03", 0); // 아이엠오
+		agentSendCounts.put("04", 0); // 비즈뿌리오
+		agentSendCounts.put("05", 0); // 제이제이
+		agentSendCounts.put("07", 0); // 인비토
+		return agentSendCounts;
+	}
+
+
+	/** 
+	 * @methodName	: calculateSendCounts 
+	 * @author		: 이호영
+	 * @date		: 2024.11.26 
+	 * @description	: agentSendCounts에 있는 에이젼트 map에 전송사 비율에 따라 카운트 put 하기
+	 * @param agentSendCounts
+	 * @param sendRate
+	 * @param finalSize 
+	 */
+	public static void calculateSendCounts(Map<String, Integer> agentSendCounts, MjonMsgVO sendRate, int finalSize) {
+		String agentCode = sendRate.getAgentCode();
+		float sendRatePercentage = Float.parseFloat(sendRate.getSendRate());
+
+		int sendCount = Math.round(finalSize * sendRatePercentage);
+
+		if (agentSendCounts.containsKey(agentCode)) {
+			agentSendCounts.put(agentCode, sendCount);
+		}
+	}
+
+	/** 
+	 * @methodName	: assignAgentBasedOnCount 
+	 * @author		: 이호영
+	 * @date		: 2024.11.26 
+	 * @description	: initializeAgentSendCounts()에서 만든 map으로 발송 agent 데이터 Set
+	 * @param agentSendCounts
+	 * @param sendRateList
+	 * @return 
+	 */
+	public static String assignAgentBasedOnCount(Map<String, Integer> agentSendCounts, List<MjonMsgVO> sendRateList) {
+		// 전송사 코드 순서대로 확인 :: initializeAgentSendCounts 메소드와 맞춰야함
+		List<String> agentCodes = Arrays.asList("01", "02", "03", "04", "05", "07");
+
+		// 전송 가능한 코드 찾기
+		for (String agentCode : agentCodes) {
+			if (agentSendCounts.get(agentCode) > 0) {
+				agentSendCounts.put(agentCode, agentSendCounts.get(agentCode) - 1);
+				return agentCode;
+			}
+		}
+
+		// 모든 코드의 전송 가능 횟수가 0이면 대표 전송사로 할당
+		return sendRateList.get(0).getRepAgent();
+	}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 }
src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataDAO.java
--- src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataDAO.java
+++ src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataDAO.java
@@ -5,6 +5,7 @@
 import org.springframework.stereotype.Repository;
 
 import egovframework.rte.psl.dataaccess.EgovAbstractDAO;
+import itn.com.cmm.MjonMsgSendVO;
 import itn.let.lett.service.LetterVO;
 import itn.let.mjo.addr.service.AddrVO;
 import itn.let.mjo.msg.service.MjonMsgVO;
@@ -371,5 +372,12 @@
 	public List<MjonMsgVO> selectPayUserSumFaxList(MjonMsgVO mjonMsgVO) throws Exception{
 		return (List<MjonMsgVO>) list("mjonMsgDAO.selectPayUserSumFaxList",mjonMsgVO);
 	}
+
+
+	public int insertMsgDataInfo_advc(List<MjonMsgSendVO> mjonMsgSendVOList) {
+		//단문 전송
+		int result = update("mjonMsgDAO.insertMsgData_advc", mjonMsgSendVOList);
+		return result;
+	}
 	
 }
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
@@ -22,12 +22,15 @@
 import org.springframework.stereotype.Service;
 import org.springframework.web.servlet.ModelAndView;
 
+import egovframework.com.idgen.CustomIdGnrService;
 import egovframework.rte.fdl.cmmn.EgovAbstractServiceImpl;
+import egovframework.rte.fdl.cmmn.exception.FdlException;
 import egovframework.rte.fdl.idgnr.EgovIdGnrService;
 import egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper;
 import egovframework.rte.psl.dataaccess.util.EgovMap;
 import itn.com.cmm.LoginVO;
 import itn.com.cmm.MjonMsgSendVO;
+import itn.com.cmm.OptimalMsgResultDTO;
 import itn.com.cmm.util.MJUtil;
 import itn.com.cmm.util.MsgSendUtils;
 import itn.com.cmm.util.StringUtil;
@@ -95,6 +98,9 @@
 
     @Resource(name = "egovMjonMsgIdGnrService")
     private EgovIdGnrService idgenMsgId;
+    
+    @Resource(name = "egovMjonMsgIdCGnrService")
+    private CustomIdGnrService idgenMsgCId;
 
     @Resource(name = "egovMjonMsgGroupIdGnrService")
     private EgovIdGnrService idgenMjonMsgGroupId;
@@ -4033,86 +4039,7 @@
 			return statusResponse;
 		}
 
-		log.debug(" + mjonMsgVO.getFileCnt() :: [{}]", mjonMsgVO.getFileCnt());
-
-		
-		/*
-		 * 화면에서 넘어오는 단가 금액 및 총 결제 캐시 금액 체크 해주기
-		 * 검증을 위해서 시스템 단가, 회원 협의 단가, 이벤트 단가 정보 조회
-		 * 조회 단가를 이용하여 총 결제 캐시 금액 계산
-		 * 파라미터로 넘어온 개별단가(eachPrice), 총 결제캐시(totPrice)를 비요하여 동일하지 않으면 컨트롤러에서 계산한 금액으로 입력해줌.
-		 */
-		
-
-		/////////////////////////////////////////////////////////////////////////////
-		// 금액 관련은 끝에서 이벤트 발송 / 기존금액 박송으로 나눠야하기 때문에 나중에 수정
-		/////////////////////////////////////////////////////////////////////////////
-		/*
-		
-		
-		// 기존 소수점 2째자리에서 반올림하였으나, 정책 변경으로 소수점 버림 처리함
-//		boolean compareEndDate = false;
-		float paramEachPrice = Float.parseFloat(mjonMsgVO.getEachPrice());
-
-		
-		
-		MjonEventVO mjonEventVO = new MjonEventVO();
-		mjonEventVO.setMberId(userId); 
-		
-		MjonEventVO eventMberInfo = mjonEventService.selectEventMsgMberDefaultInfo(mjonEventVO);
-		// 호출부에서 handleEventStatus를 호출하고 예외 처리를 추가
-		
-		// 기존 컨트롤러 2352 라인
-		if (eventMberInfo != null) {
-		    String eventEndDate = eventMberInfo.getEventEndDate();
-		    boolean compareEndDate = MJUtil.getCompareDate(eventEndDate);
-		    boolean isSuccess = handleEventStatus(eventMberInfo, paramEachPrice, mjonEventVO, compareEndDate);
-		
-		    if (!isSuccess) {
-		        // 예외가 발생했을 때의 리턴 처리
-		        return new StatusResponse(HttpStatus.INTERNAL_SERVER_ERROR, "이벤트 상태 처리 중 오류가 발생했습니다.");
-		    }
-		    // 이벤트 진행 회원의 발송 단가 처리
-		    if("Y".equals(eventMberInfo.getEventStatus()) && compareEndDate) {
-		        shortPrice = Float.parseFloat(eventMberInfo.getEventShortPrice());
-		        longPrice = Float.parseFloat(eventMberInfo.getEventLongPrice());
-		        picturePrice = Float.parseFloat(eventMberInfo.getEventPicturePrice());
-		        picture2Price = Float.parseFloat(eventMberInfo.getEventPicture2Price());
-		        picture3Price = Float.parseFloat(eventMberInfo.getEventPicture3Price());
-		    }
-		}
-		
-		
-		
-		
-
-		// 토탈금액 "," 리플레이스 처리
-		mjonMsgVO.setTotPrice(mjonMsgVO.getTotPrice().replaceAll(",", ""));
-		
-		// 각 문자 종류별 단가 설정
-		float tmpEachPrice = MsgSendUtils.determinePriceByMsgType(mjonMsgVO, shortPrice, longPrice, picturePrice, picture2Price, picture3Price);
-		mjonMsgVO.setEachPrice(Float.toString(tmpEachPrice));
-		
-		// 수신자 총 수 * 단가를 통해 총 결제 금액 계산
-		int tmpTotCallCnt = mjonMsgVO.getCallToList().length;
-		float tmpTotPrice = tmpTotCallCnt * tmpEachPrice;
-		mjonMsgVO.setTotPrice(Float.toString(tmpTotPrice));
-*/
-		/////////////////////////////////////////////////////////////////////////////
-		// 금액 관련은 끝에서 이벤트 발송 / 기존금액 박송으로 나눠야하기 때문에 나중에 수정
-		/////////////////////////////////////////////////////////////////////////////
-		
-		
-		////////////////////////////////////////////////
-		// 치환 여부
-		////////////////////////////////////////////////
 		List<MjonMsgSendVO> mjonMsgSendVOList = mjonMsgVO.getMjonMsgSendVOList();
-//		if (MsgSendUtils.isRepleasYN(mjonMsgVO)) {
-			
-//			if (!MsgSendUtils.validateReplacementData(mjonMsgVO, mjonMsgTabulVO)) {
-//				//특정문구 일괄변환 치환문자 데이터가 없습니다.
-//				return statusResponse;  // 유효성 검사 실패 시 처리
-//			}
 
 
 		// 삭제 전 리스트 크기 저장
@@ -4126,40 +4053,53 @@
 		int finalSize = mjonMsgSendVOList.size();
 		// 삭제된 건 수 계산
 		int deletedCount = initialSize - finalSize;
-//		log.info(" +삭제된 건수 ::  [{}]",deletedCount);
+		
+		
+		
+		
+		
+		
+		
 		
 
-//        				long startTime = System.nanoTime();  // 시작 시간 측정
+		// 전용 전송사 코드 및 대표 전송사 조회
+		MberManageVO userInfo = mjonMsgDataDAO.selectMberManageInfo(userId);
+		String hotlineAgentCode = userInfo.getHotlineAgentCode();
+		
+		// 핫라인 전송사 처리
+		if (!"00".equals(hotlineAgentCode)) {
+			hotlineAgentCode = handleHotlineAgent(mjonMsgVO, hotlineAgentCode, userInfo);
+			log.info("Hotline agent code processed: {}", hotlineAgentCode);
+		}
 
+		// 전송사별 전송 수 초기화
+		Map<String, Integer> agentSendCounts = MsgSendUtils.initializeAgentSendCounts();
+
+		// 수신자 총 명수 기준으로 전송사 비율 처리
+		// 전송사별 배분률 조회
+		List<MjonMsgVO> sendRateList = mjonMsgDataDAO.selectSendAgentList(mjonMsgVO);
+		
+		// finalSize :: 수신거부 번호 제거 후 총 수신자 수
+		if (finalSize >= 10) {
+			// 전송사별 발송 비율 계산
+			for (MjonMsgVO sendRate : sendRateList) {
+				MsgSendUtils.calculateSendCounts(agentSendCounts, sendRate, finalSize);
+			}
+		}
+		
+		mjonMsgVO.setAgentCode(hotlineAgentCode);
+		
 		// smstxt 치환 및 스팸체크 후  mjonMsgSendVOList 에 add()
 		List<String> resultSpamTxt = mjonMsgDataService.selectSpamKeywordList();
-		if(!MsgSendUtils.populateSendLists(mjonMsgVO, mjonMsgSendVOList, statusResponse, resultSpamTxt)) {;
+		// msgGroupId 생성
+		String nextMsgGroupId = idgenMjonMsgGroupId.getNextStringId();
+		if(!MsgSendUtils.populateSendLists(mjonMsgVO, mjonMsgSendVOList, statusResponse, resultSpamTxt
+				, agentSendCounts, sendRateList, nextMsgGroupId)) {;
 			//문자 치환 후 전송 문자 길이를 초과하였습니다.
 			//문자 치환 중 오류가 발생하였습니다.
 			return statusResponse;
 		}
-		mjonMsgSendVOList.stream().forEach(t-> System.out.println(" + ReqDate :: "+t.getReqDate()));
 
-//        long endTime = System.nanoTime();  // 종료 시간 측정
-//        long duration = endTime - startTime;  // 실행 시간 계산 (나노초)
-
-//			        // 나노초를 초와 분으로 변환
-//			        long seconds = duration / 1_000_000_000;
-//			        long minutes = seconds / 60;
-//			        seconds = seconds % 60;  // 분으로 나누고 남은 초 계산
-
-//        System.out.println("Execution time: " + minutes + " minutes " + seconds + " seconds");
-		
-
-
-
-		
-		
-		
-		
-		
-		
-		
 
 		//1.시스템 기본 단가 정보 불러오기
 		JoinSettingVO sysJoinSetVO = mjonMsgDataService.selectJoinSettingInfo();
@@ -4168,14 +4108,103 @@
 		MsgSendUtils.setPriceforVO(mjonMsgVO, mjonMsgSendVOList, sysJoinSetVO, mberManageVO);
 		
 		
+
 		
+		
+		/*mjonMsgSendVOList.parallelStream().forEach(t -> {
+			try {
+				t.setMsgId(idgenMsgId.getNextStringId());
+			} catch (FdlException e) {
+				log.error("MsgId 생성 중 오류 발생", e);
+			}
+		});*/
+		List<String> idList = idgenMsgCId.getNextStringId(mjonMsgSendVOList.size());
+//		System.out.println(" idList.size() : " + idList.size());
+//		System.out.println(" idList : " + idList);
+		for (int i = 0; i < mjonMsgSendVOList.size(); i++) {
+		    mjonMsgSendVOList.get(i).setMsgId(idList.get(i));
+		}
+
+//		mjonMsgSendVOList.stream().forEach(t-> System.out.print(t.toString()+"\n") );
+		
+
+	    
 		// 이벤트 영역
+		// 이벤트 영역
+		
+		// 이벤트 정보 가져오기
+		// 이벤트 상태가 "E"가 아닌 경우에만 호출
+		// 이벤트 금액이 있을 시 발송 LIST에서 => optimalMsgList로 데이터 이동
+		// 이동하면서 이벤트 금액으로 설정
 		MjonEventVO eventMberInfo = mjonEventService.selectEventMsgMberDefaultInfo_advc(userId);
+		OptimalMsgResultDTO result = null;
+		List<MjonMsgSendVO> optimalMsgList = new ArrayList<>();
 		
-		List<MjonMsgSendVO> optimalMsgList = MsgSendUtils.getOptimalMsgList(mjonMsgVO.getTotalPrice(), eventMberInfo.getEventRemainCash(), mjonMsgSendVOList);
-		
+		if (eventMberInfo != null && !"E".equals(eventMberInfo.getEventStatus())) {
+			try {
+				result = MsgSendUtils.getOptimalMsgList(eventMberInfo, mjonMsgSendVOList);
+				optimalMsgList = result.getOptimalMsgList();
+				MjonEventVO returnEventMberInfo = result.getEventInfo();
+	
+				if (returnEventMberInfo != null && "E".equals(returnEventMberInfo.getEventStatus())) {
+					returnEventMberInfo.setMberId(userId);
+					mjonEventService.updateEventEndStatus(returnEventMberInfo);
+				}
+	
+			} catch (IllegalArgumentException e) {
+				// 메시지 타입 에러에 대한 응답 처리
+				statusResponse.setStatus(HttpStatus.BAD_REQUEST);
+				statusResponse.setMessage("이벤트 데이터 처리 중 오류가 발생하였습니다.\n관리자에게 문의해 주세요.");
+				return statusResponse;
+			}
+		}
 		log.info(" + optimalMsgList :: [{}]", optimalMsgList.size());
-		log.info(" + mjonMsgSendVOList :: [{}]", mjonMsgSendVOList.size());
+
+		
+		
+		
+		
+
+
+		// 시작 시간 측정
+		long startTime = System.currentTimeMillis();
+		
+		
+		
+		log.info("mj_msg_data insert start [{}]", mjonMsgSendVOList.size());
+		
+		// 분할 최대건수가 되면 디비에 입력하기
+		int instCnt = mjonMsgDataDAO.insertMsgDataInfo_advc(mjonMsgSendVOList);
+
+		
+		// 종료 시간 측정
+		long endTime = System.currentTimeMillis();
+		// 실행 시간 계산 (밀리초 -> 초로 변환)
+		double executionTimeInSeconds = (endTime - startTime) / 1000.0;
+		// 실행 시간 출력
+		System.out.println("Execution time: " + executionTimeInSeconds + " seconds");
+//		mjonMsgSendVOList.stream().forEach(t-> System.out.print(t.toString()+"\n") );
+//		mjonMsgSendVOList.stream().forEach(t-> System.out.print(t.toString()+"\n") );
+		
+
+		// 강제로 IllegalArgumentException 발생시키기
+		if (true) {
+		    throw new IllegalArgumentException("강제로 발생한 오류입니다.");
+		}
+		
+		
+		
+
+		
+		
+//		log.info(" + optimalMsgList :: [{}]", optimalMsgList.size());
+//		log.info(" + optimalMsgList :: [{}]", optimalMsgList.get(0).getEachPrice());
+//		log.info(" + mjonMsgSendVOList :: [{}]", mjonMsgSendVOList.size());
+//		log.info(" + mjonMsgSendVOList :: [{}]", mjonMsgSendVOList.get(0).getEachPrice());		
+		
+		
+		
+		
 //		log.info("mjonMsgVO.getTotalPrice() :: [{}]", mjonMsgVO.getTotalPrice());
 		
 //		log.info(" + userId :: [{}]", userId);
@@ -4214,6 +4243,41 @@
 
 	}
 	
+    /** 
+     * @methodName	: handleHotlineAgent 
+     * @author		: 이호영
+     * @date		: 2024.11.26 
+     * @description	: agent 발송 비율 리스트를 가져오기위해 msgType 수정
+     * @param mjonMsgVO
+     * @param hotlineAgentCode
+     * @param userInfo
+     * @return
+     * @throws Exception 
+     */
+    private String handleHotlineAgent(MjonMsgVO mjonMsgVO, String hotlineAgentCode, MberManageVO userInfo) throws Exception {
+    	MjonMsgAgentStsVO mjonMsgAgentStsVO = new MjonMsgAgentStsVO();
+    	mjonMsgAgentStsVO.setAgentCode(hotlineAgentCode);
+
+    	// 메시지 타입에 따른 전송사 설정
+    	if ("6".equals(mjonMsgVO.getMsgType())) {
+    		int fileCount = Integer.parseInt(mjonMsgVO.getFileCnt());
+    		mjonMsgAgentStsVO.setMsgType(fileCount > 0 ? "P" : "L");
+    	} else {
+    		mjonMsgAgentStsVO.setMsgType("S");
+    	}
+
+    	// 핫라인 전송사 리스트 조회
+    	List<MjonMsgAgentStsVO> hotlineMsgAgentList = mjonMsgAgentStsDAO.selectMsgAgentListByAgentCode(mjonMsgAgentStsVO);
+
+    	// 핫라인 전송사 코드 업데이트
+    	for (MjonMsgAgentStsVO tmp : hotlineMsgAgentList) {
+    		hotlineAgentCode = MsgSendUtils.handleHotlineAgentRate(userInfo, tmp, hotlineAgentCode);
+    	}
+
+    	return hotlineAgentCode; // 최종 변경된 hotlineAgentCode 반환
+    }
+    
+    
 	private MjonMsgVO setImagesSetting(MjonMsgVO mjonMsgVO, StatusResponse statusResponse) throws Exception {
 		
 		int fileCount = 0;
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
@@ -3174,11 +3174,10 @@
 	        ModelMap model) throws Exception {
 
 	    // 디버깅 출력
-	    System.out.println("mjonMsgVO : " + mjonMsgVO.getMjonMsgSendVOList().toString());
-	    System.out.println("mjonMsgVO : " + mjonMsgVO.getMjonMsgSendVOList().size()); 
+//	    System.out.println("mjonMsgVO : " + mjonMsgVO.getMjonMsgSendVOList().toString());
+//	    System.out.println("mjonMsgVO : " + mjonMsgVO.getMjonMsgSendVOList().size()); 
 		
 		return ResponseEntity.ok().body(mjonMsgDataService.sendMsgData_advc(mjonMsgVO, request)) ;
-//		return null;
 		
 	}
 	
src/main/java/itn/let/module/base/PriceAndPoint.java
--- src/main/java/itn/let/module/base/PriceAndPoint.java
+++ src/main/java/itn/let/module/base/PriceAndPoint.java
@@ -34,6 +34,12 @@
  
 	@Autowired
 	private MjonMsgDataDAO mjonMsgDataDAO;
+	
+	@Autowired
+	private MjonPayDAO mjonPayDAO;
+
+    @Resource(name = "egovMjonCashIdGnrService")
+    private EgovIdGnrService idgenMjonCashId;
  
 	/** 
 	 * @methodName	: getBefCash 
@@ -95,10 +101,8 @@
 	 * @param msgGroupId
 	 * @throws Exception 
 	 */
-	public static void insertCashAndPoint(
-					MjonPayDAO mjonPayDAO
-					, EgovIdGnrService idgenMjonCashId
-					, String userId
+	public void insertCashAndPoint(
+					 String userId
 					, float totPrice
 					, String memo
 					, String msgGroupId
src/main/resources/egovframework/spring/com/context-idgen.xml
--- src/main/resources/egovframework/spring/com/context-idgen.xml
+++ src/main/resources/egovframework/spring/com/context-idgen.xml
@@ -2331,6 +2331,31 @@
 		<property name="fillChar" value="0" />
 	</bean>
 	
+	
+	
+	<!-- 문자온 개선 작업(advc) 중 대량 발송을 위한 ids
+			 문자ID Generation Strategy Config -->
+	<bean id="egovMjonMsgIdCGnrService"
+	    class="egovframework.com.idgen.impl.CustomTableIdGnrServiceImpl">
+	    <property name="dataSource" ref="dataSource" />
+	    <property name="blockSize" value="1000"/>
+	    <property name="table" value="IDS"/> <!-- 실제 테이블 이름 -->
+	    <property name="tableName" value="MSGC_ID"/> <!-- TABLE_NAME 컬럼의 값 -->
+	    <!-- <property name="columnName" value="NEXT_ID"/> NEXT_ID 컬럼명 -->
+	    <property name="prefix" value="MSGCID_"/>
+	    <property name="cipers" value="14"/>
+	    <property name="fillChar" value="0"/>
+	    <property name="applyYear" value="false"/>
+	    <!-- 필요에 따라 추가 프로퍼티 설정 -->
+	</bean>
+
+	
+	
+	
+	
+	
+	
+	
 	<!-- 문자의 장문,단문, 그림문자 등록   ID Generation  Strategy Config -->
 	<bean name="egovLetterInfoIdGnrService"
 		class="egovframework.rte.fdl.idgnr.impl.EgovTableIdGnrServiceImpl"
src/main/resources/egovframework/sqlmap/let/msg/MjonMsgData_SQL_mysql.xml
--- src/main/resources/egovframework/sqlmap/let/msg/MjonMsgData_SQL_mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/msg/MjonMsgData_SQL_mysql.xml
@@ -3,7 +3,7 @@
   =========     =======    =================================================
   2021.03.01    신명섭 	
 -->
-<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
+<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
 <sqlMap namespace="Msg">
 	<typeAlias  alias="mjonMsgVO" type="itn.let.mjo.msg.service.MjonMsgVO"/>
 	<typeAlias  alias="mjonResvMsgVO" type="itn.let.mjo.reservmsg.service.MjonResvMsgVO"/>
@@ -153,6 +153,37 @@
 	       )
        </iterate>
 	</insert>
+		
+	<insert id="mjonMsgDAO.insertMsgData_advc" parameterClass="java.util.List">
+		INSERT	INTO   MJ_MSG_DATA
+       	( MSG_ID,
+       	 MSG_GROUP_ID,
+       	 USER_ID, 
+       	 AGENT_CODE,
+         CUR_STATE,
+         REQ_DATE,
+         CALL_TO,
+         CALL_FROM,
+         SUBJECT,
+         SMS_TXT,
+         MSG_TYPE
+       )VALUES
+       	<iterate conjunction=",">
+	       (
+	          #[].msgId#,
+	          #[].msgGroupId#,
+	          #[].userId#,
+	          #[].agentCode#,
+	          0,
+	          #[].reqDate#,
+	          #[].callTo#,
+	          #[].callFrom#,
+	          #[].subject#,
+	          #[].smsTxt#,
+	          #[].msgType#
+	       )
+       </iterate>
+	</insert>
 	
 	<select id="mjonMsgDAO.selectMjonMsgList" parameterClass="mjonMsgVO" resultClass="mjonMsgVO">
 		SELECT 
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
@@ -2207,7 +2207,8 @@
 			|| contents.indexOf("[*3*]") > -1
 			|| contents.indexOf("[*4*]") > -1){
 		
-		fnReplCell();
+// 		fnReplCell();
+		fnReplCell_advc();
 		
 	}else{
 		
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
@@ -1105,6 +1105,7 @@
 	if(eventStatus == 'Y' 
 				&& parseFloat(eventRemainCash) < parseFloat(totPriceOnly))
 	{
+		console.log('eventRemainCash : ', eventRemainCash);
 		if(!confirm("고객님의 이벤트 캐시 잔액(" + eventRemainCash + "원) 부족으로 요청하신 문자("+numberWithCommas($selectedData.length)+"건)를 모두 발송할 수 없습니다. 이벤트 캐시 초과 건에 대해서는 일반 단가를 적용하여 발송하시겠습니까?")){
 			return false;
 		}
@@ -1288,11 +1289,16 @@
 					
 					$('.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>문자가 발송 되었습니다.");
+					var resText = (reserYn === 'Y') ? '예약' : '발송';
+					if(reserYn == 'Y')
+					/* {
+						resText = "예약";
 					}
+					else
+					{
+						resText = "발송";
+					} */
+					$('.pop_msg_success .msg_text').html(resText+" 성공 : <strong>"+ smsCnt + "</strong>건,수신거부 : <span>" + blockCnt + "</span>건의<br>문자가 "+resText+" 되었습니다.");
 					
 					$('.mask').addClass('on');
 					
src/main/webapp/js/txtSpecialReplace.js
--- src/main/webapp/js/txtSpecialReplace.js
+++ src/main/webapp/js/txtSpecialReplace.js
@@ -1,3 +1,59 @@
+
+function fnReplCell_advc(){
+	
+	var smsTxtArea = $('#smsTxtArea').val();
+	console.log('smsTxtArea : ', smsTxtArea);
+	var orgSmsTxt = smsTxtArea;		// 광고문자 뺀 문자내용
+	
+	var recTableData = tableL.getRows();		 // 받는사람 리스트의 전체 목록을 저장
+	
+	var smsTxt = smsTxtArea;
+	
+	var data = recTableData[0].getData();
+	
+	if(typeof data.name != 'undefined' 
+					&& data.name != null 
+					&& data.name != ""){
+		
+		var name = data.name;
+		smsTxt = stringReplaceAll(smsTxt, "[*이름*]", name);
+		
+		
+	}
+	if(typeof data.rep1 != 'undefined' && data.rep1 != null && data.rep1 != ""){
+		
+		var rep1 = data.rep1;
+		smsTxt = stringReplaceAll(smsTxt, "[*1*]", rep1);
+		
+	}
+	
+	if(typeof data.rep2 != 'undefined' && data.rep2 != null  && data.rep2 != ""){
+		
+		var rep2 = data.rep2;
+		smsTxt = stringReplaceAll(smsTxt, "[*2*]", rep2);
+		
+	}
+	
+	if(typeof data.rep3 != 'undefined' && data.rep3 != null  && data.rep3 != ""){
+		
+		var rep3 = data.rep3;
+		smsTxt = stringReplaceAll(smsTxt, "[*3*]", rep3);
+		
+	}
+	
+	if(typeof data.rep4 != 'undefined' && data.rep4 != null && data.rep4 != ""){
+		
+		var rep4 = data.rep4;
+		smsTxt = stringReplaceAll(smsTxt, "[*4*]", rep4);
+		
+	}
+	
+	console.log('smsTxt : ', smsTxt);
+	$('.realtime').text(smsTxt);
+
+	
+}
+
 //특정문구 일괄변환 문자길이 체크 하기
 function fnReplCell(){
 	
@@ -86,7 +142,9 @@
 				
 				var smsTxt = smsTxtArea;
 				
-				if(typeof recTableData[j].getData().name != 'undefined' && recTableData[j].getData().name != null && recTableData[j].getData().name != ""){
+				if(typeof recTableData[j].getData().name != 'undefined' 
+				&& recTableData[j].getData().name != null 
+				&& recTableData[j].getData().name != ""){
 					
 //					console.log('name : ', name);
 //					console.log('smsTxt : ', smsTxt);
@@ -343,7 +401,10 @@
 			for(var j=0; j < recTableData.length; j++){
 				
 				var smsTxt = smsTxtArea;
-				if(typeof recTableData[j].getData().name != 'undefined' && recTableData[j].getData().name != null && recTableData[j].getData().name != ""){
+				if(typeof recTableData[j].getData().name != 'undefined' 
+						&& recTableData[j].getData().name != null 
+						&& recTableData[j].getData().name != "")
+				{
 					
 					var name = recTableData[j].getData().name;
 					smsTxt = stringReplaceAll(smsTxt, "[*이름*]", name);
Add a comment
List