전화 관련 commit
This commit is contained in:
215
src/main/java/com/madeu/crm/callLog/ctrl/CallLogController.java
Normal file
215
src/main/java/com/madeu/crm/callLog/ctrl/CallLogController.java
Normal file
@@ -0,0 +1,215 @@
|
||||
package com.madeu.crm.callLog.ctrl;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.madeu.constants.Constants;
|
||||
import com.madeu.crm.callLog.dto.CallLogSearchDTO;
|
||||
import com.madeu.crm.callLog.dto.CallMemoDTO;
|
||||
import com.madeu.crm.callLog.service.CallLogService;
|
||||
import com.madeu.crm.callLog.service.GoodArsService;
|
||||
import com.madeu.init.ManagerDraftAction;
|
||||
import com.madeu.util.HttpUtil;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequestMapping("/callLog")
|
||||
public class CallLogController extends ManagerDraftAction {
|
||||
|
||||
@Autowired
|
||||
private CallLogService callLogService;
|
||||
|
||||
@Autowired
|
||||
private GoodArsService goodArsService;
|
||||
|
||||
/**
|
||||
* 통화 로그 관리 화면으로 이동
|
||||
*/
|
||||
@RequestMapping("/moveCallLogList.do")
|
||||
public String moveCallLogList(HttpSession session, HttpServletRequest request, HttpServletResponse response,
|
||||
Model model) {
|
||||
log.debug("CallLogController moveCallLogList START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = null;
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return "/web/login/logout";
|
||||
} else {
|
||||
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = callLogService.moveCallLogList(paramMap);
|
||||
model.addAttribute("selectUseYn", map.get("selectUseYn"));
|
||||
model.addAttribute("insertUseYn", map.get("insertUseYn"));
|
||||
model.addAttribute("updateUseYn", map.get("updateUseYn"));
|
||||
model.addAttribute("deleteUseYn", map.get("deleteUseYn"));
|
||||
model.addAttribute("downloadUseYn", map.get("downloadUseYn"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "/web/login/logout";
|
||||
}
|
||||
log.debug("CallLogController moveCallLogList END");
|
||||
|
||||
return "/web/callLog/callLogSelectList";
|
||||
}
|
||||
|
||||
/**
|
||||
* 통화 로그 목록 조회 (통계 포함)
|
||||
*/
|
||||
@PostMapping("/getCallLogList.do")
|
||||
public ModelAndView getCallLogList(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response, CallLogSearchDTO searchDTO) {
|
||||
log.debug("CallLogController getCallLogList START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
searchDTO.setLoginMemberId(String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = callLogService.getCallLogList(searchDTO);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "서버 오류가 발생했습니다.");
|
||||
} finally {
|
||||
if (Constants.OK != map.get("msgCode")) {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", false);
|
||||
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug("CallLogController getCallLogList END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 통화 메모 저장
|
||||
*/
|
||||
@PostMapping("/saveCallMemo.do")
|
||||
public ModelAndView saveCallMemo(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response, CallMemoDTO memoDTO) {
|
||||
log.debug("CallLogController saveCallMemo START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
memoDTO.setLoginMemberId(String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = callLogService.saveCallMemo(memoDTO);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "메모 저장 중 오류가 발생하였습니다.");
|
||||
}
|
||||
log.debug("CallLogController saveCallMemo END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 전화 걸기 (아웃바운드)
|
||||
*/
|
||||
@PostMapping("/makeOutboundCall.do")
|
||||
public ModelAndView makeOutboundCall(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
log.debug("CallLogController makeOutboundCall START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
String loginCid = String.valueOf(paramMap.get("loginCid"));
|
||||
String sendCid = String.valueOf(paramMap.get("sendCid"));
|
||||
String userCid = String.valueOf(paramMap.get("userCid"));
|
||||
|
||||
map = goodArsService.makeOutboundCall(loginCid, sendCid, userCid);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
map.put("success", false);
|
||||
map.put("msgDesc", "전화 발신 중 오류가 발생하였습니다.");
|
||||
}
|
||||
log.debug("CallLogController makeOutboundCall END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 전화 돌려주기 (Push)
|
||||
*/
|
||||
@PostMapping("/pushCall.do")
|
||||
public ModelAndView pushCall(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
log.debug("CallLogController pushCall START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
String loginCid = String.valueOf(paramMap.get("loginCid"));
|
||||
String pushCid = String.valueOf(paramMap.get("pushCid"));
|
||||
|
||||
map = goodArsService.pushCall(loginCid, pushCid);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
map.put("success", false);
|
||||
map.put("msgDesc", "전화 연결 중 오류가 발생하였습니다.");
|
||||
}
|
||||
log.debug("CallLogController pushCall END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 녹음 파일 URL 조회
|
||||
*/
|
||||
@PostMapping("/getRecordFileUrl.do")
|
||||
public ModelAndView getRecordFileUrl(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
log.debug("CallLogController getRecordFileUrl START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
String recordNo = String.valueOf(paramMap.get("recordNo"));
|
||||
String url = goodArsService.getRecordFileUrl(recordNo);
|
||||
map.put("success", true);
|
||||
map.put("recordUrl", url);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
map.put("success", false);
|
||||
map.put("msgDesc", "녹음 파일 조회 중 오류가 발생하였습니다.");
|
||||
}
|
||||
log.debug("CallLogController getRecordFileUrl END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package com.madeu.crm.callLog.ctrl;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.madeu.crm.callLog.dto.CallLogDTO;
|
||||
import com.madeu.crm.callLog.service.CallLogService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* GoodARS Webhook 수신 Controller
|
||||
*
|
||||
* ARS 서버에서 통화 단계별로 이 엔드포인트를 호출하여 통화 로그를 저장합니다.
|
||||
* 인증 없이(public) 접근 가능해야 합니다.
|
||||
*
|
||||
* 통화 흐름:
|
||||
* step01 (수신) → step02 (ARS 메뉴 선택) → step03 (통화 연결)
|
||||
* → step04 (통화 종료) → step05 (기타 종료)
|
||||
* callback (콜백 요청)
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/cti/webhook")
|
||||
public class CtiWebhookController {
|
||||
|
||||
@Autowired
|
||||
private CallLogService callLogService;
|
||||
|
||||
/**
|
||||
* Step 01 - 전화 수신 (통화 시작)
|
||||
*/
|
||||
@PostMapping("/step01")
|
||||
public String step01(
|
||||
@RequestParam(value = "FULLDNIS", required = false, defaultValue = "") String fulldnis,
|
||||
@RequestParam(value = "CID", required = false, defaultValue = "") String cid,
|
||||
@RequestParam(value = "MENU_NO", required = false, defaultValue = "") String menuNo,
|
||||
@RequestParam(value = "LOGINCID", required = false, defaultValue = "") String logincid,
|
||||
@RequestParam(value = "RECORD_NO", required = false, defaultValue = "") String recordNo,
|
||||
@RequestParam(value = "BOUND", required = false, defaultValue = "") String bound,
|
||||
@RequestParam(value = "STATE_TYPE", required = false, defaultValue = "") String stateType) {
|
||||
|
||||
log.debug("CtiWebhook step01 - RECORD_NO:{}, CID:{}, FULLDNIS:{}", recordNo, cid, fulldnis);
|
||||
|
||||
try {
|
||||
CallLogDTO logDTO = buildLogDTO(fulldnis, cid, menuNo, logincid, recordNo, bound, stateType, "1");
|
||||
callLogService.putCtiLog(logDTO);
|
||||
return "success";
|
||||
} catch (Exception e) {
|
||||
log.error("CtiWebhook step01 error", e);
|
||||
return "fail";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 02 - ARS 메뉴 선택 (Ring)
|
||||
*/
|
||||
@PostMapping("/step02")
|
||||
public String step02(
|
||||
@RequestParam(value = "FULLDNIS", required = false, defaultValue = "") String fulldnis,
|
||||
@RequestParam(value = "CID", required = false, defaultValue = "") String cid,
|
||||
@RequestParam(value = "MENU_NO", required = false, defaultValue = "") String menuNo,
|
||||
@RequestParam(value = "LOGINCID", required = false, defaultValue = "") String logincid,
|
||||
@RequestParam(value = "RECORD_NO", required = false, defaultValue = "") String recordNo,
|
||||
@RequestParam(value = "BOUND", required = false, defaultValue = "") String bound,
|
||||
@RequestParam(value = "STATE_TYPE", required = false, defaultValue = "") String stateType) {
|
||||
|
||||
log.debug("CtiWebhook step02 - RECORD_NO:{}, MENU_NO:{}", recordNo, menuNo);
|
||||
|
||||
try {
|
||||
CallLogDTO logDTO = buildLogDTO(fulldnis, cid, menuNo, logincid, recordNo, bound, stateType, "2");
|
||||
callLogService.putCtiLog(logDTO);
|
||||
return "success";
|
||||
} catch (Exception e) {
|
||||
log.error("CtiWebhook step02 error", e);
|
||||
return "fail";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 03 - 통화 연결 (Link)
|
||||
*/
|
||||
@PostMapping("/step03")
|
||||
public String step03(
|
||||
@RequestParam(value = "FULLDNIS", required = false, defaultValue = "") String fulldnis,
|
||||
@RequestParam(value = "CID", required = false, defaultValue = "") String cid,
|
||||
@RequestParam(value = "MENU_NO", required = false, defaultValue = "") String menuNo,
|
||||
@RequestParam(value = "LOGINCID", required = false, defaultValue = "") String logincid,
|
||||
@RequestParam(value = "RECORD_NO", required = false, defaultValue = "") String recordNo,
|
||||
@RequestParam(value = "BOUND", required = false, defaultValue = "") String bound,
|
||||
@RequestParam(value = "STATE_TYPE", required = false, defaultValue = "") String stateType) {
|
||||
|
||||
log.debug("CtiWebhook step03 - RECORD_NO:{}, LOGINCID:{}", recordNo, logincid);
|
||||
|
||||
try {
|
||||
CallLogDTO logDTO = buildLogDTO(fulldnis, cid, menuNo, logincid, recordNo, bound, stateType, "3");
|
||||
callLogService.putCtiLog(logDTO);
|
||||
return "success";
|
||||
} catch (Exception e) {
|
||||
log.error("CtiWebhook step03 error", e);
|
||||
return "fail";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 04 - 통화 종료 (Link End)
|
||||
*/
|
||||
@PostMapping("/step04")
|
||||
public String step04(
|
||||
@RequestParam(value = "FULLDNIS", required = false, defaultValue = "") String fulldnis,
|
||||
@RequestParam(value = "CID", required = false, defaultValue = "") String cid,
|
||||
@RequestParam(value = "MENU_NO", required = false, defaultValue = "") String menuNo,
|
||||
@RequestParam(value = "LOGINCID", required = false, defaultValue = "") String logincid,
|
||||
@RequestParam(value = "RECORD_NO", required = false, defaultValue = "") String recordNo,
|
||||
@RequestParam(value = "BOUND", required = false, defaultValue = "") String bound,
|
||||
@RequestParam(value = "STATE_TYPE", required = false, defaultValue = "") String stateType) {
|
||||
|
||||
log.debug("CtiWebhook step04 - RECORD_NO:{}", recordNo);
|
||||
|
||||
try {
|
||||
CallLogDTO logDTO = buildLogDTO(fulldnis, cid, menuNo, logincid, recordNo, bound, stateType, "4");
|
||||
callLogService.putCtiLog(logDTO);
|
||||
return "success";
|
||||
} catch (Exception e) {
|
||||
log.error("CtiWebhook step04 error", e);
|
||||
return "fail";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 05 - 기타 종료 (Bend)
|
||||
*/
|
||||
@PostMapping("/step05")
|
||||
public String step05(
|
||||
@RequestParam(value = "FULLDNIS", required = false, defaultValue = "") String fulldnis,
|
||||
@RequestParam(value = "CID", required = false, defaultValue = "") String cid,
|
||||
@RequestParam(value = "MENU_NO", required = false, defaultValue = "") String menuNo,
|
||||
@RequestParam(value = "LOGINCID", required = false, defaultValue = "") String logincid,
|
||||
@RequestParam(value = "RECORD_NO", required = false, defaultValue = "") String recordNo,
|
||||
@RequestParam(value = "BOUND", required = false, defaultValue = "") String bound,
|
||||
@RequestParam(value = "STATE_TYPE", required = false, defaultValue = "") String stateType) {
|
||||
|
||||
log.debug("CtiWebhook step05 - RECORD_NO:{}", recordNo);
|
||||
|
||||
try {
|
||||
CallLogDTO logDTO = buildLogDTO(fulldnis, cid, menuNo, logincid, recordNo, bound, stateType, "5");
|
||||
callLogService.putCtiLog(logDTO);
|
||||
return "success";
|
||||
} catch (Exception e) {
|
||||
log.error("CtiWebhook step05 error", e);
|
||||
return "fail";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback - 콜백 요청
|
||||
*/
|
||||
@PostMapping("/callback")
|
||||
public String callback(
|
||||
@RequestParam(value = "FULLDNIS", required = false, defaultValue = "") String fulldnis,
|
||||
@RequestParam(value = "CID", required = false, defaultValue = "") String cid,
|
||||
@RequestParam(value = "MENU_NO", required = false, defaultValue = "") String menuNo,
|
||||
@RequestParam(value = "LOGINCID", required = false, defaultValue = "") String logincid,
|
||||
@RequestParam(value = "RECORD_NO", required = false, defaultValue = "") String recordNo,
|
||||
@RequestParam(value = "BOUND", required = false, defaultValue = "") String bound,
|
||||
@RequestParam(value = "STATE_TYPE", required = false, defaultValue = "") String stateType) {
|
||||
|
||||
log.debug("CtiWebhook callback - RECORD_NO:{}, CID:{}", recordNo, cid);
|
||||
|
||||
try {
|
||||
CallLogDTO logDTO = buildLogDTO(fulldnis, cid, menuNo, logincid, recordNo, bound, "callback", "98");
|
||||
callLogService.putCtiLog(logDTO);
|
||||
return "success";
|
||||
} catch (Exception e) {
|
||||
log.error("CtiWebhook callback error", e);
|
||||
return "fail";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통 DTO 빌드 메서드
|
||||
*/
|
||||
private CallLogDTO buildLogDTO(String fulldnis, String cid, String menuNo, String logincid,
|
||||
String recordNo, String bound, String stateType, String ctiStep) {
|
||||
CallLogDTO dto = new CallLogDTO();
|
||||
dto.setFulldnis(fulldnis);
|
||||
dto.setCid(cid);
|
||||
dto.setMenuNo(menuNo);
|
||||
dto.setLogincid(logincid);
|
||||
dto.setRecordNo(recordNo);
|
||||
dto.setBound(bound);
|
||||
dto.setStateType(stateType);
|
||||
dto.setCtiStep(ctiStep);
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
44
src/main/java/com/madeu/crm/callLog/dto/CallLogDTO.java
Normal file
44
src/main/java/com/madeu/crm/callLog/dto/CallLogDTO.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.madeu.crm.callLog.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 통화 로그 결과 DTO
|
||||
*/
|
||||
@Data
|
||||
public class CallLogDTO {
|
||||
/* ---- PK / 기본 필드 ---- */
|
||||
private String muCtiLogId;
|
||||
private String recordNo;
|
||||
private String cid; // 발신자 전화번호
|
||||
private String fulldnis; // 매장 수신번호
|
||||
private String bound; // IN/OUT
|
||||
private String menuNo; // ARS 메뉴 번호
|
||||
private String stateType; // 상태유형
|
||||
private String ctiStep; // 통화단계
|
||||
private String logincid; // 상담원 전화번호
|
||||
private String cType1; // 초진/재진
|
||||
private String cType2; // 상담/상담후예약/회차예약
|
||||
private String crmMbPid; // CRM 회원 PID
|
||||
private String storePid; // 매장 PID
|
||||
private String incallSel; // In Call 선택값
|
||||
private String callBackChk; // 콜백 확인 여부
|
||||
private String useYn;
|
||||
private String cudFlag;
|
||||
private String regId;
|
||||
private String regDate;
|
||||
private String modId;
|
||||
private String modDate;
|
||||
|
||||
/* ---- 조인/서브쿼리 결과 필드 ---- */
|
||||
private String ring; // ARS 메뉴 선택 (step2)
|
||||
private String linkDate; // 통화 연결 시각 (step3)
|
||||
private String linkCid; // 통화 연결 상담원 (step3)
|
||||
private String linkEndDate; // 통화 종료 시각 (step4)
|
||||
private String bendDate; // 기타 종료 시각 (step5)
|
||||
private String callbackCid; // 콜백 전화번호 (step98)
|
||||
private String smsFlag; // SMS 전송 여부
|
||||
private String callMsg; // 통화 메모
|
||||
private String memberName; // 고객명
|
||||
private String rowNum; // 행 번호
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.madeu.crm.callLog.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 통화 로그 검색 조건 DTO
|
||||
*/
|
||||
@Data
|
||||
public class CallLogSearchDTO {
|
||||
/* ---- 검색 조건 ---- */
|
||||
private String sDate; // 검색 시작일
|
||||
private String eDate; // 검색 종료일
|
||||
private String callType; // 구분1 (ARS 메뉴: 1=상담, 2=예약, 3=위치안내, callback)
|
||||
private String callType1; // 구분2 (초진/재진)
|
||||
private String callType2; // 구분3 (상담/상담후예약/회차예약)
|
||||
private String mCid; // 고객 전화번호 검색
|
||||
private String fulldnis; // 매장 수신번호
|
||||
private String incallSel; // In Call 선택값 필터
|
||||
|
||||
/* ---- 페이징 ---- */
|
||||
private Integer gridLimitStart;
|
||||
private Integer gridLimitEnd;
|
||||
private String gridSort;
|
||||
|
||||
/* ---- 권한/세션 ---- */
|
||||
private String menuClass;
|
||||
private String loginMemberId;
|
||||
}
|
||||
18
src/main/java/com/madeu/crm/callLog/dto/CallLogStatsDTO.java
Normal file
18
src/main/java/com/madeu/crm/callLog/dto/CallLogStatsDTO.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.madeu.crm.callLog.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 통화 로그 통계 DTO
|
||||
*/
|
||||
@Data
|
||||
public class CallLogStatsDTO {
|
||||
private int totalCnt; // 전체 건수
|
||||
private int inCnt; // IN 건수
|
||||
private int outCnt; // OUT 건수
|
||||
private int type1Cnt; // 초진 건수
|
||||
private int type2Cnt; // 재진 건수
|
||||
private int type11Cnt; // 상담 건수
|
||||
private int type12Cnt; // 상담후예약 건수
|
||||
private int type13Cnt; // 회차예약 건수
|
||||
}
|
||||
14
src/main/java/com/madeu/crm/callLog/dto/CallMemoDTO.java
Normal file
14
src/main/java/com/madeu/crm/callLog/dto/CallMemoDTO.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.madeu.crm.callLog.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 통화 메모 DTO
|
||||
*/
|
||||
@Data
|
||||
public class CallMemoDTO {
|
||||
private String muCtiTextId;
|
||||
private String recordNo;
|
||||
private String callMsg;
|
||||
private String loginMemberId;
|
||||
}
|
||||
38
src/main/java/com/madeu/crm/callLog/map/CallLogMAP.java
Normal file
38
src/main/java/com/madeu/crm/callLog/map/CallLogMAP.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package com.madeu.crm.callLog.map;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import com.madeu.crm.callLog.dto.CallLogDTO;
|
||||
import com.madeu.crm.callLog.dto.CallLogSearchDTO;
|
||||
import com.madeu.crm.callLog.dto.CallLogStatsDTO;
|
||||
import com.madeu.crm.callLog.dto.CallMemoDTO;
|
||||
|
||||
@Mapper
|
||||
public interface CallLogMAP {
|
||||
// 통화 로그 목록 건수 조회
|
||||
int getCallLogCnt(CallLogSearchDTO searchDTO);
|
||||
|
||||
// 통화 로그 목록 조회
|
||||
List<CallLogDTO> getCallLogList(CallLogSearchDTO searchDTO);
|
||||
|
||||
// 통화 로그 통계 집계
|
||||
CallLogStatsDTO getCallLogStats(CallLogSearchDTO searchDTO);
|
||||
|
||||
// 통화 메모 존재 여부 확인
|
||||
int getCallMemoCnt(CallMemoDTO memoDTO);
|
||||
|
||||
// 통화 메모 신규 저장
|
||||
int saveCallMemo(CallMemoDTO memoDTO);
|
||||
|
||||
// 통화 메모 수정
|
||||
int modCallMemo(CallMemoDTO memoDTO);
|
||||
|
||||
// CTI 로그 저장 (Webhook용)
|
||||
int putCtiLog(CallLogDTO logDTO);
|
||||
|
||||
// CTI 로그 회원 매핑 업데이트
|
||||
int modCtiLogMember(HashMap<String, Object> paramMap);
|
||||
}
|
||||
134
src/main/java/com/madeu/crm/callLog/service/CallLogService.java
Normal file
134
src/main/java/com/madeu/crm/callLog/service/CallLogService.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package com.madeu.crm.callLog.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.madeu.common.service.MenuAuthService;
|
||||
import com.madeu.constants.Constants;
|
||||
import com.madeu.crm.callLog.dto.CallLogDTO;
|
||||
import com.madeu.crm.callLog.dto.CallLogSearchDTO;
|
||||
import com.madeu.crm.callLog.dto.CallLogStatsDTO;
|
||||
import com.madeu.crm.callLog.dto.CallMemoDTO;
|
||||
import com.madeu.crm.callLog.map.CallLogMAP;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CallLogService {
|
||||
|
||||
@Autowired
|
||||
private CallLogMAP callLogMAP;
|
||||
|
||||
@Autowired
|
||||
private MenuAuthService menuAuthService;
|
||||
|
||||
/**
|
||||
* 통화 로그 화면 이동
|
||||
*/
|
||||
public HashMap<String, Object> moveCallLogList(HashMap<String, Object> paramMap) throws Exception {
|
||||
log.debug("moveCallLogList START");
|
||||
|
||||
HashMap<String, Object> map = menuAuthService.getMenuAuthority(paramMap);
|
||||
|
||||
log.debug("loginMemberId : " + paramMap.get("loginMemberId"));
|
||||
log.debug("menuClass : " + paramMap.get("menuClass"));
|
||||
log.debug("moveCallLogList END");
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 통화 로그 목록 조회 (통계 포함)
|
||||
*/
|
||||
public HashMap<String, Object> getCallLogList(CallLogSearchDTO searchDTO) throws Exception {
|
||||
log.debug("getCallLogList START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
List<CallLogDTO> listMap = new ArrayList<>();
|
||||
|
||||
int totalCount = callLogMAP.getCallLogCnt(searchDTO);
|
||||
|
||||
if (0 < totalCount) {
|
||||
listMap = callLogMAP.getCallLogList(searchDTO);
|
||||
}
|
||||
|
||||
// 통계 집계
|
||||
CallLogStatsDTO stats = callLogMAP.getCallLogStats(searchDTO);
|
||||
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
map.put("totalCount", totalCount);
|
||||
map.put("rows", listMap);
|
||||
map.put("stats", stats);
|
||||
|
||||
log.debug("getCallLogList END");
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 통화 메모 저장
|
||||
*/
|
||||
public HashMap<String, Object> saveCallMemo(CallMemoDTO memoDTO) throws Exception {
|
||||
log.debug("saveCallMemo START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
int existCnt = callLogMAP.getCallMemoCnt(memoDTO);
|
||||
int result = 0;
|
||||
|
||||
if (existCnt > 0) {
|
||||
// 기존 메모 수정
|
||||
result = callLogMAP.modCallMemo(memoDTO);
|
||||
} else {
|
||||
// 신규 메모 등록
|
||||
result = callLogMAP.saveCallMemo(memoDTO);
|
||||
}
|
||||
|
||||
if (0 < result) {
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
map.put("msgDesc", "메모가 저장되었습니다.");
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", "false");
|
||||
map.put("msgDesc", "메모 저장에 실패하였습니다.");
|
||||
}
|
||||
|
||||
log.debug("saveCallMemo END");
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* CTI 로그 저장 (Webhook에서 호출)
|
||||
*/
|
||||
public HashMap<String, Object> putCtiLog(CallLogDTO logDTO) throws Exception {
|
||||
log.debug("putCtiLog START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
int result = callLogMAP.putCtiLog(logDTO);
|
||||
|
||||
if (0 < result) {
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", "false");
|
||||
}
|
||||
|
||||
log.debug("putCtiLog END");
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* CTI 로그 회원 매핑
|
||||
*/
|
||||
public void updateCtiLogMember(HashMap<String, Object> paramMap) throws Exception {
|
||||
callLogMAP.modCtiLogMember(paramMap);
|
||||
}
|
||||
}
|
||||
129
src/main/java/com/madeu/crm/callLog/service/GoodArsService.java
Normal file
129
src/main/java/com/madeu/crm/callLog/service/GoodArsService.java
Normal file
@@ -0,0 +1,129 @@
|
||||
package com.madeu.crm.callLog.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* GoodARS CTI API 연동 서비스
|
||||
*
|
||||
* 연동 서버: ars.goodars.co.kr
|
||||
* 고객사 ID(CPID): application.properties에 설정
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class GoodArsService {
|
||||
|
||||
@Value("${goodars.cpid:5000036030017}")
|
||||
private String arsCpid;
|
||||
|
||||
@Value("${goodars.base-url:http://ars.goodars.co.kr/LINK/MADEU}")
|
||||
private String arsBaseUrl;
|
||||
|
||||
@Value("${goodars.base-url-ssl:https://ars.goodars.co.kr/LINK/MADEU}")
|
||||
private String arsBaseUrlSsl;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
/**
|
||||
* 아웃바운드 전화 발신
|
||||
*
|
||||
* @param loginCid 상담원 전화번호 (내선번호)
|
||||
* @param sendCid 매장 대표번호
|
||||
* @param userCid 고객 전화번호
|
||||
* @return 성공 여부 ("SUCCESS" = 성공)
|
||||
*/
|
||||
public HashMap<String, Object> makeOutboundCall(String loginCid, String sendCid, String userCid) {
|
||||
log.debug("makeOutboundCall START - loginCid:{}, sendCid:{}, userCid:{}", loginCid, sendCid, userCid);
|
||||
|
||||
HashMap<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
String url = UriComponentsBuilder.fromHttpUrl(arsBaseUrl + "/wid_outcall.asp")
|
||||
.queryParam("CPID", arsCpid)
|
||||
.queryParam("LOGINCID", loginCid)
|
||||
.queryParam("SENDCID", sendCid)
|
||||
.queryParam("USERCID", userCid.replaceAll("[^0-9]", ""))
|
||||
.toUriString();
|
||||
|
||||
log.debug("makeOutboundCall URL: {}", url);
|
||||
|
||||
String response = restTemplate.getForObject(url, String.class);
|
||||
log.debug("makeOutboundCall response: {}", response);
|
||||
|
||||
if ("SUCCESS".equals(response)) {
|
||||
result.put("success", true);
|
||||
result.put("msgDesc", "전화 발신 중입니다.");
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("msgDesc", "전화 발신에 실패하였습니다. (" + response + ")");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("makeOutboundCall error", e);
|
||||
result.put("success", false);
|
||||
result.put("msgDesc", "전화 발신 중 오류가 발생하였습니다.");
|
||||
}
|
||||
|
||||
log.debug("makeOutboundCall END");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 녹음 파일 URL 생성
|
||||
*
|
||||
* @param recordNo CTI 레코드 번호
|
||||
* @return 녹음 파일 URL
|
||||
*/
|
||||
public String getRecordFileUrl(String recordNo) {
|
||||
return UriComponentsBuilder.fromHttpUrl(arsBaseUrl + "/wid_file.asp")
|
||||
.queryParam("CPID", arsCpid)
|
||||
.queryParam("SN", recordNo)
|
||||
.toUriString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 전화 돌려주기 (Push)
|
||||
*
|
||||
* @param loginCid 상담원 전화번호
|
||||
* @param pushCid 전달받을 상담원 전화번호
|
||||
* @return 성공 여부
|
||||
*/
|
||||
public HashMap<String, Object> pushCall(String loginCid, String pushCid) {
|
||||
log.debug("pushCall START - loginCid:{}, pushCid:{}", loginCid, pushCid);
|
||||
|
||||
HashMap<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
String url = UriComponentsBuilder.fromHttpUrl(arsBaseUrlSsl + "/wid_push.asp")
|
||||
.queryParam("CPID", arsCpid)
|
||||
.queryParam("LOGINCID", loginCid)
|
||||
.queryParam("PUSHCID", pushCid)
|
||||
.toUriString();
|
||||
|
||||
log.debug("pushCall URL: {}", url);
|
||||
|
||||
String response = restTemplate.getForObject(url, String.class);
|
||||
log.debug("pushCall response: {}", response);
|
||||
|
||||
if ("1".equals(response)) {
|
||||
result.put("success", true);
|
||||
result.put("msgDesc", "전화가 연결되었습니다.");
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("msgDesc", "연결에 실패하였습니다.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("pushCall error", e);
|
||||
result.put("success", false);
|
||||
result.put("msgDesc", "전화 연결 중 오류가 발생하였습니다.");
|
||||
}
|
||||
|
||||
log.debug("pushCall END");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package com.madeu.crm.smsTemplate.ctrl;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.madeu.constants.Constants;
|
||||
import com.madeu.crm.smsTemplate.dto.SmsTemplateDTO;
|
||||
import com.madeu.crm.smsTemplate.dto.SmsTemplateSearchDTO;
|
||||
import com.madeu.crm.smsTemplate.service.SmsTemplateService;
|
||||
import com.madeu.init.ManagerDraftAction;
|
||||
import com.madeu.util.HttpUtil;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequestMapping("/smsTemplate")
|
||||
public class SmsTemplateController extends ManagerDraftAction {
|
||||
|
||||
@Autowired
|
||||
private SmsTemplateService smsTemplateService;
|
||||
|
||||
/**
|
||||
* 문자 상용구 관리 화면으로 이동
|
||||
*/
|
||||
@RequestMapping("/moveSmsTemplateList.do")
|
||||
public String moveSmsTemplateList(HttpSession session, HttpServletRequest request, HttpServletResponse response,
|
||||
Model model) {
|
||||
log.debug("SmsTemplateController moveSmsTemplateList START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = null;
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return "/web/login/logout";
|
||||
} else {
|
||||
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = smsTemplateService.moveSmsTemplateList(paramMap);
|
||||
model.addAttribute("selectUseYn", map.get("selectUseYn"));
|
||||
model.addAttribute("insertUseYn", map.get("insertUseYn"));
|
||||
model.addAttribute("updateUseYn", map.get("updateUseYn"));
|
||||
model.addAttribute("deleteUseYn", map.get("deleteUseYn"));
|
||||
model.addAttribute("downloadUseYn", map.get("downloadUseYn"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "/web/login/logout";
|
||||
}
|
||||
log.debug("SmsTemplateController moveSmsTemplateList END");
|
||||
|
||||
return "/web/smsTemplate/smsTemplateSelectList";
|
||||
}
|
||||
|
||||
/**
|
||||
* 문자 상용구 목록 조회
|
||||
*/
|
||||
@PostMapping("/getSmsTemplateList.do")
|
||||
public ModelAndView getSmsTemplateList(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response, SmsTemplateSearchDTO searchDTO) {
|
||||
log.debug("SmsTemplateController getSmsTemplateList START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
searchDTO.setLoginMemberId(String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = smsTemplateService.getSmsTemplateList(searchDTO);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "서버 오류가 발생했습니다.");
|
||||
} finally {
|
||||
if (Constants.OK != map.get("msgCode")) {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", false);
|
||||
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug("SmsTemplateController getSmsTemplateList END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 문자 상용구 상세 조회
|
||||
*/
|
||||
@PostMapping("/getSmsTemplate.do")
|
||||
public ModelAndView getSmsTemplate(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response, SmsTemplateDTO dto) {
|
||||
log.debug("SmsTemplateController getSmsTemplate START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
dto.setLoginMemberId(String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = smsTemplateService.getSmsTemplate(dto);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "조회 중 오류가 발생하였습니다.");
|
||||
}
|
||||
log.debug("SmsTemplateController getSmsTemplate END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 문자 상용구 등록
|
||||
*/
|
||||
@PostMapping("/putSmsTemplate.do")
|
||||
public ModelAndView putSmsTemplate(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response, SmsTemplateDTO dto) {
|
||||
log.debug("SmsTemplateController putSmsTemplate START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
dto.setLoginMemberId(String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = smsTemplateService.putSmsTemplate(dto);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "등록 중 오류가 발생하였습니다.");
|
||||
}
|
||||
log.debug("SmsTemplateController putSmsTemplate END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 문자 상용구 수정
|
||||
*/
|
||||
@PostMapping("/modSmsTemplate.do")
|
||||
public ModelAndView modSmsTemplate(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response, SmsTemplateDTO dto) {
|
||||
log.debug("SmsTemplateController modSmsTemplate START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
dto.setLoginMemberId(String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = smsTemplateService.modSmsTemplate(dto);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "수정 중 오류가 발생하였습니다.");
|
||||
}
|
||||
log.debug("SmsTemplateController modSmsTemplate END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 문자 상용구 삭제
|
||||
*/
|
||||
@PostMapping("/delSmsTemplate.do")
|
||||
public ModelAndView delSmsTemplate(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response, SmsTemplateDTO dto) {
|
||||
log.debug("SmsTemplateController delSmsTemplate START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
dto.setLoginMemberId(String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = smsTemplateService.delSmsTemplate(dto);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "삭제 중 오류가 발생하였습니다.");
|
||||
}
|
||||
log.debug("SmsTemplateController delSmsTemplate END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.madeu.crm.smsTemplate.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* SMS 상용구 결과 DTO
|
||||
*/
|
||||
@Data
|
||||
public class SmsTemplateDTO {
|
||||
/* ---- 테이블 필드 ---- */
|
||||
private String muSmsTemplateId;
|
||||
private String title;
|
||||
private String content;
|
||||
private String useYn;
|
||||
private String cudFlag;
|
||||
private String regId;
|
||||
private String regDate;
|
||||
private String modId;
|
||||
private String modDate;
|
||||
|
||||
/* ---- 조인/서브쿼리 결과 필드 ---- */
|
||||
private String regName; // 등록자명
|
||||
private String rowNum; // 행 번호
|
||||
|
||||
/* ---- 세션/공통 ---- */
|
||||
private String loginMemberId;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.madeu.crm.smsTemplate.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* SMS 상용구 검색 조건 DTO
|
||||
*/
|
||||
@Data
|
||||
public class SmsTemplateSearchDTO {
|
||||
/* ---- 검색 조건 ---- */
|
||||
private String searchKeyword;
|
||||
|
||||
/* ---- 페이징 ---- */
|
||||
private Integer gridLimitStart;
|
||||
private Integer gridLimitEnd;
|
||||
private String gridSort;
|
||||
|
||||
/* ---- 권한/세션 ---- */
|
||||
private String menuClass;
|
||||
private String loginMemberId;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.madeu.crm.smsTemplate.map;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import com.madeu.crm.smsTemplate.dto.SmsTemplateDTO;
|
||||
import com.madeu.crm.smsTemplate.dto.SmsTemplateSearchDTO;
|
||||
|
||||
@Mapper
|
||||
public interface SmsTemplateMAP {
|
||||
// 상용구 목록 건수 조회
|
||||
int getSmsTemplateCnt(SmsTemplateSearchDTO searchDTO);
|
||||
|
||||
// 상용구 목록 조회
|
||||
List<SmsTemplateDTO> getSmsTemplateList(SmsTemplateSearchDTO searchDTO);
|
||||
|
||||
// 상용구 상세 조회
|
||||
SmsTemplateDTO getSmsTemplate(SmsTemplateDTO dto);
|
||||
|
||||
// 상용구 등록
|
||||
int putSmsTemplate(SmsTemplateDTO dto);
|
||||
|
||||
// 상용구 수정
|
||||
int modSmsTemplate(SmsTemplateDTO dto);
|
||||
|
||||
// 상용구 삭제 (논리 삭제)
|
||||
int delSmsTemplate(SmsTemplateDTO dto);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.madeu.crm.smsTemplate.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.madeu.common.service.MenuAuthService;
|
||||
import com.madeu.constants.Constants;
|
||||
import com.madeu.crm.smsTemplate.dto.SmsTemplateDTO;
|
||||
import com.madeu.crm.smsTemplate.dto.SmsTemplateSearchDTO;
|
||||
import com.madeu.crm.smsTemplate.map.SmsTemplateMAP;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SmsTemplateService {
|
||||
|
||||
@Autowired
|
||||
private SmsTemplateMAP smsTemplateMAP;
|
||||
|
||||
@Autowired
|
||||
private MenuAuthService menuAuthService;
|
||||
|
||||
/**
|
||||
* 상용구 관리 화면 이동
|
||||
*/
|
||||
public HashMap<String, Object> moveSmsTemplateList(HashMap<String, Object> paramMap) throws Exception {
|
||||
log.debug("moveSmsTemplateList START");
|
||||
|
||||
HashMap<String, Object> map = menuAuthService.getMenuAuthority(paramMap);
|
||||
|
||||
log.debug("loginMemberId : " + paramMap.get("loginMemberId"));
|
||||
log.debug("menuClass : " + paramMap.get("menuClass"));
|
||||
log.debug("moveSmsTemplateList END");
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 상용구 목록 조회
|
||||
*/
|
||||
public HashMap<String, Object> getSmsTemplateList(SmsTemplateSearchDTO searchDTO) throws Exception {
|
||||
log.debug("getSmsTemplateList START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
List<SmsTemplateDTO> listMap = new ArrayList<>();
|
||||
|
||||
int totalCount = smsTemplateMAP.getSmsTemplateCnt(searchDTO);
|
||||
|
||||
if (0 < totalCount) {
|
||||
listMap = smsTemplateMAP.getSmsTemplateList(searchDTO);
|
||||
}
|
||||
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
map.put("totalCount", totalCount);
|
||||
map.put("rows", listMap);
|
||||
|
||||
log.debug("getSmsTemplateList END");
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 상용구 상세 조회
|
||||
*/
|
||||
public HashMap<String, Object> getSmsTemplate(SmsTemplateDTO dto) throws Exception {
|
||||
log.debug("getSmsTemplate START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
SmsTemplateDTO detail = smsTemplateMAP.getSmsTemplate(dto);
|
||||
|
||||
if (detail != null) {
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
map.put("data", detail);
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", "false");
|
||||
map.put("msgDesc", "상용구 정보를 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
log.debug("getSmsTemplate END");
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 상용구 등록
|
||||
*/
|
||||
public HashMap<String, Object> putSmsTemplate(SmsTemplateDTO dto) throws Exception {
|
||||
log.debug("putSmsTemplate START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
int result = smsTemplateMAP.putSmsTemplate(dto);
|
||||
|
||||
if (0 < result) {
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
map.put("msgDesc", "상용구가 등록되었습니다.");
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", "false");
|
||||
map.put("msgDesc", "상용구 등록에 실패하였습니다.");
|
||||
}
|
||||
|
||||
log.debug("putSmsTemplate END");
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 상용구 수정
|
||||
*/
|
||||
public HashMap<String, Object> modSmsTemplate(SmsTemplateDTO dto) throws Exception {
|
||||
log.debug("modSmsTemplate START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
int result = smsTemplateMAP.modSmsTemplate(dto);
|
||||
|
||||
if (0 < result) {
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
map.put("msgDesc", "상용구가 수정되었습니다.");
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", "false");
|
||||
map.put("msgDesc", "상용구 수정에 실패하였습니다.");
|
||||
}
|
||||
|
||||
log.debug("modSmsTemplate END");
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 상용구 삭제 (논리 삭제)
|
||||
*/
|
||||
public HashMap<String, Object> delSmsTemplate(SmsTemplateDTO dto) throws Exception {
|
||||
log.debug("delSmsTemplate START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
int result = smsTemplateMAP.delSmsTemplate(dto);
|
||||
|
||||
if (0 < result) {
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
map.put("msgDesc", "상용구가 삭제되었습니다.");
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", "false");
|
||||
map.put("msgDesc", "상용구 삭제에 실패하였습니다.");
|
||||
}
|
||||
|
||||
log.debug("delSmsTemplate END");
|
||||
return map;
|
||||
}
|
||||
}
|
||||
319
src/main/resources/mappers/crm/callLog/CallLogSql.xml
Normal file
319
src/main/resources/mappers/crm/callLog/CallLogSql.xml
Normal file
@@ -0,0 +1,319 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.madeu.crm.callLog.map.CallLogMAP">
|
||||
|
||||
<!-- 통화 로그 목록 건수 조회 -->
|
||||
<select id="getCallLogCnt" parameterType="com.madeu.crm.callLog.dto.CallLogSearchDTO" resultType="Integer">
|
||||
/** CallLogMAP.getCallLogCnt **/
|
||||
SELECT COUNT(*) AS TOTAL_COUNT
|
||||
FROM MU_CTI_LOG A
|
||||
WHERE A.USE_YN = 'Y'
|
||||
AND A.FULLDNIS = #{fulldnis}
|
||||
AND A.REG_DATE >= CONCAT(#{sDate}, ' 00:00:00')
|
||||
AND A.REG_DATE <= CONCAT(#{eDate}, ' 23:59:59')
|
||||
<if test="callType != null and callType != '' and callType != 'callback'">
|
||||
AND A.MENU_NO = #{callType}
|
||||
AND A.STATE_TYPE IN ('start', 'ringout')
|
||||
</if>
|
||||
<if test="callType == 'callback'">
|
||||
AND A.STATE_TYPE = 'callback'
|
||||
</if>
|
||||
<if test="callType == null or callType == ''">
|
||||
AND (A.STATE_TYPE = 'start' OR A.STATE_TYPE = 'ringout')
|
||||
</if>
|
||||
<if test="callType1 != null and callType1 != ''">
|
||||
AND A.C_TYPE1 = #{callType1}
|
||||
</if>
|
||||
<if test="callType2 != null and callType2 != ''">
|
||||
AND A.C_TYPE2 = #{callType2}
|
||||
</if>
|
||||
<if test="mCid != null and mCid != ''">
|
||||
AND A.CID LIKE CONCAT('%', #{mCid}, '%')
|
||||
</if>
|
||||
<if test="incallSel != null and incallSel != ''">
|
||||
AND A.INCALL_SEL LIKE CONCAT('%|=|', #{incallSel}, '|=|%')
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- 통화 로그 통계 집계 -->
|
||||
<select id="getCallLogStats" parameterType="com.madeu.crm.callLog.dto.CallLogSearchDTO" resultType="com.madeu.crm.callLog.dto.CallLogStatsDTO">
|
||||
/** CallLogMAP.getCallLogStats **/
|
||||
SELECT COUNT(*) AS "totalCnt"
|
||||
,SUM(IF(A.BOUND = 'IN', 1, 0)) AS "inCnt"
|
||||
,SUM(IF(A.BOUND = 'OUT', 1, 0)) AS "outCnt"
|
||||
,SUM(IF(A.C_TYPE1 = '초진', 1, 0)) AS "type1Cnt"
|
||||
,SUM(IF(A.C_TYPE1 = '재진', 1, 0)) AS "type2Cnt"
|
||||
,SUM(IF(A.C_TYPE2 = '상담', 1, 0)) AS "type11Cnt"
|
||||
,SUM(IF(A.C_TYPE2 = '상담후예약', 1, 0)) AS "type12Cnt"
|
||||
,SUM(IF(A.C_TYPE2 = '회차예약', 1, 0)) AS "type13Cnt"
|
||||
FROM MU_CTI_LOG A
|
||||
WHERE A.USE_YN = 'Y'
|
||||
AND A.FULLDNIS = #{fulldnis}
|
||||
AND A.REG_DATE >= CONCAT(#{sDate}, ' 00:00:00')
|
||||
AND A.REG_DATE <= CONCAT(#{eDate}, ' 23:59:59')
|
||||
<if test="callType != null and callType != '' and callType != 'callback'">
|
||||
AND A.MENU_NO = #{callType}
|
||||
AND A.STATE_TYPE IN ('start', 'ringout')
|
||||
</if>
|
||||
<if test="callType == 'callback'">
|
||||
AND A.STATE_TYPE = 'callback'
|
||||
</if>
|
||||
<if test="callType == null or callType == ''">
|
||||
AND (A.STATE_TYPE = 'start' OR A.STATE_TYPE = 'ringout')
|
||||
</if>
|
||||
<if test="callType1 != null and callType1 != ''">
|
||||
AND A.C_TYPE1 = #{callType1}
|
||||
</if>
|
||||
<if test="callType2 != null and callType2 != ''">
|
||||
AND A.C_TYPE2 = #{callType2}
|
||||
</if>
|
||||
<if test="mCid != null and mCid != ''">
|
||||
AND A.CID LIKE CONCAT('%', #{mCid}, '%')
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- 통화 연결 건수 (통계용) -->
|
||||
<select id="getCallLogLinkedStats" parameterType="com.madeu.crm.callLog.dto.CallLogSearchDTO" resultType="hashmap">
|
||||
/** CallLogMAP.getCallLogLinkedStats **/
|
||||
SELECT COUNT(*) AS "totalOkCnt"
|
||||
,SUM(IF(A.BOUND = 'IN', 1, 0)) AS "inOkCnt"
|
||||
,SUM(IF(A.BOUND = 'OUT', 1, 0)) AS "outOkCnt"
|
||||
FROM MU_CTI_LOG A
|
||||
WHERE A.USE_YN = 'Y'
|
||||
AND A.FULLDNIS = #{fulldnis}
|
||||
AND A.REG_DATE >= CONCAT(#{sDate}, ' 00:00:00')
|
||||
AND A.REG_DATE <= CONCAT(#{eDate}, ' 23:59:59')
|
||||
AND A.STATE_TYPE = 'link'
|
||||
</select>
|
||||
|
||||
<!-- 통화 로그 목록 조회 -->
|
||||
<select id="getCallLogList" parameterType="com.madeu.crm.callLog.dto.CallLogSearchDTO" resultType="com.madeu.crm.callLog.dto.CallLogDTO">
|
||||
/** CallLogMAP.getCallLogList **/
|
||||
SELECT L.*
|
||||
FROM (
|
||||
SELECT L.*
|
||||
,CAST(@RNUM:=@RNUM + 1 AS CHAR) AS "rowNum"
|
||||
FROM (
|
||||
SELECT A.MU_CTI_LOG_ID AS "muCtiLogId"
|
||||
,A.RECORD_NO AS "recordNo"
|
||||
,A.CID AS "cid"
|
||||
,A.BOUND AS "bound"
|
||||
,A.MENU_NO AS "menuNo"
|
||||
,A.STATE_TYPE AS "stateType"
|
||||
,A.C_TYPE1 AS "cType1"
|
||||
,A.C_TYPE2 AS "cType2"
|
||||
,A.CRM_MB_PID AS "crmMbPid"
|
||||
,A.STORE_PID AS "storePid"
|
||||
,A.INCALL_SEL AS "incallSel"
|
||||
,A.CALL_BACK_CHK AS "callBackChk"
|
||||
,DATE_FORMAT(A.REG_DATE, '%Y-%m-%d %H:%i:%s') AS "regDate"
|
||||
/* ring - ARS 메뉴 선택 */
|
||||
,(SELECT B.MENU_NO
|
||||
FROM MU_CTI_LOG B
|
||||
WHERE B.RECORD_NO = A.RECORD_NO
|
||||
AND B.CTI_STEP = '2'
|
||||
AND B.USE_YN = 'Y'
|
||||
ORDER BY B.REG_DATE DESC LIMIT 1) AS "ring"
|
||||
/* 통화 연결 시각 */
|
||||
,(SELECT DATE_FORMAT(B.REG_DATE, '%Y-%m-%d %H:%i:%s')
|
||||
FROM MU_CTI_LOG B
|
||||
WHERE B.RECORD_NO = A.RECORD_NO
|
||||
AND B.CTI_STEP = '3'
|
||||
AND B.USE_YN = 'Y'
|
||||
ORDER BY B.REG_DATE DESC LIMIT 1) AS "linkDate"
|
||||
/* 통화 연결 상담원 */
|
||||
,(SELECT B.LOGINCID
|
||||
FROM MU_CTI_LOG B
|
||||
WHERE B.RECORD_NO = A.RECORD_NO
|
||||
AND B.CTI_STEP = '3'
|
||||
AND B.USE_YN = 'Y'
|
||||
ORDER BY B.REG_DATE DESC LIMIT 1) AS "linkCid"
|
||||
/* 통화 종료 시각 (step 4) */
|
||||
,(SELECT DATE_FORMAT(B.REG_DATE, '%Y-%m-%d %H:%i:%s')
|
||||
FROM MU_CTI_LOG B
|
||||
WHERE B.RECORD_NO = A.RECORD_NO
|
||||
AND B.CTI_STEP = '4'
|
||||
AND B.USE_YN = 'Y'
|
||||
ORDER BY B.REG_DATE DESC LIMIT 1) AS "linkEndDate"
|
||||
/* 통화 종료 시각 (step 5) */
|
||||
,(SELECT DATE_FORMAT(B.REG_DATE, '%Y-%m-%d %H:%i:%s')
|
||||
FROM MU_CTI_LOG B
|
||||
WHERE B.RECORD_NO = A.RECORD_NO
|
||||
AND B.CTI_STEP = '5'
|
||||
AND B.USE_YN = 'Y'
|
||||
ORDER BY B.REG_DATE DESC LIMIT 1) AS "bendDate"
|
||||
/* callback CID */
|
||||
,(SELECT B.CID
|
||||
FROM MU_CTI_LOG B
|
||||
WHERE B.RECORD_NO = A.RECORD_NO
|
||||
AND B.CTI_STEP = '98'
|
||||
AND B.USE_YN = 'Y'
|
||||
ORDER BY B.REG_DATE DESC LIMIT 1) AS "callbackCid"
|
||||
/* SMS 전송 여부 */
|
||||
,(SELECT B.STATE_TYPE
|
||||
FROM MU_CTI_LOG B
|
||||
WHERE B.RECORD_NO = A.RECORD_NO
|
||||
AND B.STATE_TYPE = 'lms'
|
||||
AND B.USE_YN = 'Y'
|
||||
ORDER BY B.REG_DATE DESC LIMIT 1) AS "smsFlag"
|
||||
/* 통화 메모 */
|
||||
,(SELECT T.CALL_MSG
|
||||
FROM MU_CTI_TEXT T
|
||||
WHERE T.RECORD_NO = A.RECORD_NO
|
||||
AND T.USE_YN = 'Y'
|
||||
ORDER BY T.REG_DATE DESC LIMIT 1) AS "callMsg"
|
||||
/* 고객명 */
|
||||
,(SELECT MM.NAME
|
||||
FROM MU_MEMBER MM
|
||||
WHERE MM.MU_MEMBER_ID = A.CRM_MB_PID
|
||||
AND MM.USE_YN = 'Y'
|
||||
LIMIT 1) AS "memberName"
|
||||
FROM MU_CTI_LOG A
|
||||
WHERE A.USE_YN = 'Y'
|
||||
AND A.FULLDNIS = #{fulldnis}
|
||||
AND A.REG_DATE >= CONCAT(#{sDate}, ' 00:00:00')
|
||||
AND A.REG_DATE <= CONCAT(#{eDate}, ' 23:59:59')
|
||||
<if test="callType != null and callType != '' and callType != 'callback'">
|
||||
AND A.MENU_NO = #{callType}
|
||||
AND A.STATE_TYPE IN ('start', 'ringout')
|
||||
</if>
|
||||
<if test="callType == 'callback'">
|
||||
AND A.STATE_TYPE = 'callback'
|
||||
</if>
|
||||
<if test="callType == null or callType == ''">
|
||||
AND (A.STATE_TYPE = 'start' OR A.STATE_TYPE = 'ringout')
|
||||
</if>
|
||||
<if test="callType1 != null and callType1 != ''">
|
||||
AND A.C_TYPE1 = #{callType1}
|
||||
</if>
|
||||
<if test="callType2 != null and callType2 != ''">
|
||||
AND A.C_TYPE2 = #{callType2}
|
||||
</if>
|
||||
<if test="mCid != null and mCid != ''">
|
||||
AND A.CID LIKE CONCAT('%', #{mCid}, '%')
|
||||
</if>
|
||||
<if test="incallSel != null and incallSel != ''">
|
||||
AND A.INCALL_SEL LIKE CONCAT('%|=|', #{incallSel}, '|=|%')
|
||||
</if>
|
||||
ORDER BY A.REG_DATE DESC
|
||||
LIMIT 18446744073709551615
|
||||
) L, (SELECT @RNUM:=0) R
|
||||
WHERE 1 = 1
|
||||
) L
|
||||
WHERE 1 = 1
|
||||
<if test="gridLimitEnd != null and gridLimitEnd != ''">
|
||||
LIMIT ${gridLimitStart}, ${gridLimitEnd}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- 통화 메모 존재 여부 -->
|
||||
<select id="getCallMemoCnt" parameterType="com.madeu.crm.callLog.dto.CallMemoDTO" resultType="Integer">
|
||||
/** CallLogMAP.getCallMemoCnt **/
|
||||
SELECT COUNT(*)
|
||||
FROM MU_CTI_TEXT
|
||||
WHERE RECORD_NO = #{recordNo}
|
||||
AND USE_YN = 'Y'
|
||||
</select>
|
||||
|
||||
<!-- 통화 메모 신규 저장 -->
|
||||
<insert id="saveCallMemo" parameterType="com.madeu.crm.callLog.dto.CallMemoDTO">
|
||||
<selectKey resultType="string" keyProperty="muCtiTextId" order="BEFORE">
|
||||
SELECT CONCAT('CTXT', LPAD(IFNULL(MAX(CAST(SUBSTRING(MU_CTI_TEXT_ID, 5) AS UNSIGNED)), 0) + 1, 11, '0'))
|
||||
FROM MU_CTI_TEXT
|
||||
</selectKey>
|
||||
/** CallLogMAP.saveCallMemo **/
|
||||
INSERT INTO MU_CTI_TEXT (
|
||||
MU_CTI_TEXT_ID
|
||||
,RECORD_NO
|
||||
,CALL_MSG
|
||||
,USE_YN
|
||||
,CUD_FLAG
|
||||
,REG_ID
|
||||
,REG_DATE
|
||||
,MOD_ID
|
||||
,MOD_DATE
|
||||
) VALUES (
|
||||
#{muCtiTextId}
|
||||
,#{recordNo}
|
||||
,#{callMsg}
|
||||
,'Y'
|
||||
,'C'
|
||||
,#{loginMemberId}
|
||||
,NOW()
|
||||
,#{loginMemberId}
|
||||
,NOW()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- 통화 메모 수정 -->
|
||||
<update id="modCallMemo" parameterType="com.madeu.crm.callLog.dto.CallMemoDTO">
|
||||
/** CallLogMAP.modCallMemo **/
|
||||
UPDATE MU_CTI_TEXT
|
||||
SET CALL_MSG = #{callMsg}
|
||||
,CUD_FLAG = 'U'
|
||||
,MOD_ID = #{loginMemberId}
|
||||
,MOD_DATE = NOW()
|
||||
WHERE RECORD_NO = #{recordNo}
|
||||
AND USE_YN = 'Y'
|
||||
</update>
|
||||
|
||||
<!-- CTI 로그 저장 (Webhook용) -->
|
||||
<insert id="putCtiLog" parameterType="com.madeu.crm.callLog.dto.CallLogDTO">
|
||||
<selectKey resultType="string" keyProperty="muCtiLogId" order="BEFORE">
|
||||
SELECT CONCAT('CLOG', LPAD(IFNULL(MAX(CAST(SUBSTRING(MU_CTI_LOG_ID, 5) AS UNSIGNED)), 0) + 1, 11, '0'))
|
||||
FROM MU_CTI_LOG
|
||||
</selectKey>
|
||||
/** CallLogMAP.putCtiLog **/
|
||||
INSERT INTO MU_CTI_LOG (
|
||||
MU_CTI_LOG_ID
|
||||
,RECORD_NO
|
||||
,CID
|
||||
,FULLDNIS
|
||||
,BOUND
|
||||
,MENU_NO
|
||||
,STATE_TYPE
|
||||
,CTI_STEP
|
||||
,LOGINCID
|
||||
,C_TYPE1
|
||||
,C_TYPE2
|
||||
,CRM_MB_PID
|
||||
,STORE_PID
|
||||
,INCALL_SEL
|
||||
,USE_YN
|
||||
,CUD_FLAG
|
||||
,REG_DATE
|
||||
,MOD_DATE
|
||||
) VALUES (
|
||||
#{muCtiLogId}
|
||||
,#{recordNo}
|
||||
,#{cid}
|
||||
,#{fulldnis}
|
||||
,#{bound}
|
||||
,#{menuNo}
|
||||
,#{stateType}
|
||||
,#{ctiStep}
|
||||
,#{logincid}
|
||||
,#{cType1}
|
||||
,#{cType2}
|
||||
,#{crmMbPid}
|
||||
,#{storePid}
|
||||
,#{incallSel}
|
||||
,'Y'
|
||||
,'C'
|
||||
,NOW()
|
||||
,NOW()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- CTI 로그 회원 매핑 업데이트 -->
|
||||
<update id="modCtiLogMember" parameterType="hashmap">
|
||||
/** CallLogMAP.modCtiLogMember **/
|
||||
UPDATE MU_CTI_LOG
|
||||
SET STORE_PID = #{storePid}
|
||||
,CRM_MB_PID = #{crmMbPid}
|
||||
,MOD_DATE = NOW()
|
||||
WHERE RECORD_NO = #{recordNo}
|
||||
AND USE_YN = 'Y'
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
133
src/main/resources/mappers/crm/smsTemplate/SmsTemplateSql.xml
Normal file
133
src/main/resources/mappers/crm/smsTemplate/SmsTemplateSql.xml
Normal file
@@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.madeu.crm.smsTemplate.map.SmsTemplateMAP">
|
||||
|
||||
<!-- 상용구 목록 건수 조회 -->
|
||||
<select id="getSmsTemplateCnt" parameterType="com.madeu.crm.smsTemplate.dto.SmsTemplateSearchDTO" resultType="Integer">
|
||||
/** SmsTemplateMAP.getSmsTemplateCnt **/
|
||||
SELECT COUNT(*) AS TOTAL_COUNT
|
||||
FROM MU_SMS_TEMPLATE
|
||||
WHERE USE_YN = 'Y'
|
||||
<if test="searchKeyword != null and searchKeyword != ''">
|
||||
AND (TITLE LIKE CONCAT('%', TRIM(#{searchKeyword}), '%')
|
||||
OR CONTENT LIKE CONCAT('%', TRIM(#{searchKeyword}), '%'))
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- 상용구 목록 조회 -->
|
||||
<select id="getSmsTemplateList" parameterType="com.madeu.crm.smsTemplate.dto.SmsTemplateSearchDTO" resultType="com.madeu.crm.smsTemplate.dto.SmsTemplateDTO">
|
||||
/** SmsTemplateMAP.getSmsTemplateList **/
|
||||
SELECT ST.*
|
||||
FROM (
|
||||
SELECT ST.*
|
||||
,CAST(@RNUM:=@RNUM + 1 AS CHAR) AS "rowNum"
|
||||
FROM (
|
||||
SELECT ST.MU_SMS_TEMPLATE_ID AS "muSmsTemplateId"
|
||||
,ST.TITLE AS "title"
|
||||
,ST.CONTENT AS "content"
|
||||
,DATE_FORMAT(ST.REG_DATE, '%Y-%m-%d %H:%i') AS "regDate"
|
||||
,DATE_FORMAT(ST.MOD_DATE, '%Y-%m-%d %H:%i') AS "modDate"
|
||||
,(SELECT MM.NAME
|
||||
FROM MU_MEMBER AS MM
|
||||
WHERE MM.USE_YN = 'Y'
|
||||
AND MM.MU_MEMBER_ID = ST.REG_ID
|
||||
LIMIT 0, 1) AS "regName"
|
||||
FROM MU_SMS_TEMPLATE AS ST
|
||||
WHERE ST.USE_YN = 'Y'
|
||||
<if test="searchKeyword != null and searchKeyword != ''">
|
||||
AND (ST.TITLE LIKE CONCAT('%', TRIM(#{searchKeyword}), '%')
|
||||
OR ST.CONTENT LIKE CONCAT('%', TRIM(#{searchKeyword}), '%'))
|
||||
</if>
|
||||
<choose>
|
||||
<when test="gridSort != null and gridSort != ''">
|
||||
ORDER BY ${gridSort}
|
||||
</when>
|
||||
<otherwise>
|
||||
ORDER BY ST.REG_DATE DESC
|
||||
</otherwise>
|
||||
</choose>
|
||||
LIMIT 18446744073709551615
|
||||
) ST, (SELECT @RNUM:=0) R
|
||||
WHERE 1 = 1
|
||||
) ST
|
||||
WHERE 1 = 1
|
||||
<if test="gridLimitEnd != null and gridLimitEnd != ''">
|
||||
LIMIT ${gridLimitStart}, ${gridLimitEnd}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- 상용구 상세 조회 -->
|
||||
<select id="getSmsTemplate" parameterType="com.madeu.crm.smsTemplate.dto.SmsTemplateDTO" resultType="com.madeu.crm.smsTemplate.dto.SmsTemplateDTO">
|
||||
/** SmsTemplateMAP.getSmsTemplate **/
|
||||
SELECT ST.MU_SMS_TEMPLATE_ID AS "muSmsTemplateId"
|
||||
,ST.TITLE AS "title"
|
||||
,ST.CONTENT AS "content"
|
||||
,DATE_FORMAT(ST.REG_DATE, '%Y-%m-%d %H:%i') AS "regDate"
|
||||
,DATE_FORMAT(ST.MOD_DATE, '%Y-%m-%d %H:%i') AS "modDate"
|
||||
,(SELECT MM.NAME
|
||||
FROM MU_MEMBER AS MM
|
||||
WHERE MM.USE_YN = 'Y'
|
||||
AND MM.MU_MEMBER_ID = ST.REG_ID
|
||||
LIMIT 0, 1) AS "regName"
|
||||
FROM MU_SMS_TEMPLATE AS ST
|
||||
WHERE ST.USE_YN = 'Y'
|
||||
AND ST.MU_SMS_TEMPLATE_ID = #{muSmsTemplateId}
|
||||
LIMIT 0, 1
|
||||
</select>
|
||||
|
||||
<!-- 상용구 등록 -->
|
||||
<insert id="putSmsTemplate" parameterType="com.madeu.crm.smsTemplate.dto.SmsTemplateDTO">
|
||||
<selectKey resultType="string" keyProperty="muSmsTemplateId" order="BEFORE">
|
||||
SELECT CONCAT('SMST', LPAD(IFNULL(MAX(CAST(SUBSTRING(MU_SMS_TEMPLATE_ID, 5) AS UNSIGNED)), 0) + 1, 11, '0'))
|
||||
FROM MU_SMS_TEMPLATE
|
||||
</selectKey>
|
||||
/** SmsTemplateMAP.putSmsTemplate **/
|
||||
INSERT INTO MU_SMS_TEMPLATE (
|
||||
MU_SMS_TEMPLATE_ID
|
||||
,TITLE
|
||||
,CONTENT
|
||||
,USE_YN
|
||||
,CUD_FLAG
|
||||
,REG_ID
|
||||
,REG_DATE
|
||||
,MOD_ID
|
||||
,MOD_DATE
|
||||
) VALUES (
|
||||
#{muSmsTemplateId}
|
||||
,#{title}
|
||||
,#{content}
|
||||
,'Y'
|
||||
,'C'
|
||||
,#{loginMemberId}
|
||||
,NOW()
|
||||
,#{loginMemberId}
|
||||
,NOW()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- 상용구 수정 -->
|
||||
<update id="modSmsTemplate" parameterType="com.madeu.crm.smsTemplate.dto.SmsTemplateDTO">
|
||||
/** SmsTemplateMAP.modSmsTemplate **/
|
||||
UPDATE MU_SMS_TEMPLATE
|
||||
SET TITLE = #{title}
|
||||
,CONTENT = #{content}
|
||||
,CUD_FLAG = 'U'
|
||||
,MOD_ID = #{loginMemberId}
|
||||
,MOD_DATE = NOW()
|
||||
WHERE USE_YN = 'Y'
|
||||
AND MU_SMS_TEMPLATE_ID = #{muSmsTemplateId}
|
||||
</update>
|
||||
|
||||
<!-- 상용구 삭제 (논리 삭제) -->
|
||||
<update id="delSmsTemplate" parameterType="com.madeu.crm.smsTemplate.dto.SmsTemplateDTO">
|
||||
/** SmsTemplateMAP.delSmsTemplate **/
|
||||
UPDATE MU_SMS_TEMPLATE
|
||||
SET USE_YN = 'N'
|
||||
,CUD_FLAG = 'D'
|
||||
,MOD_ID = #{loginMemberId}
|
||||
,MOD_DATE = NOW()
|
||||
WHERE USE_YN = 'Y'
|
||||
AND MU_SMS_TEMPLATE_ID = #{muSmsTemplateId}
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
350
src/main/resources/static/css/web/call_log.css
Normal file
350
src/main/resources/static/css/web/call_log.css
Normal file
@@ -0,0 +1,350 @@
|
||||
/* ============================================
|
||||
통화 로그 관리 (call_log.css)
|
||||
============================================ */
|
||||
|
||||
/* ---- 검색 필터 영역 ---- */
|
||||
.cl_filter_wrap {
|
||||
background: #fff;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 10px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.cl_filter_row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cl_filter_row+.cl_filter_row {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #F0F1F3;
|
||||
}
|
||||
|
||||
.cl_filter_item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.cl_filter_item label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
white-space: nowrap;
|
||||
min-width: 55px;
|
||||
}
|
||||
|
||||
.cl_filter_item select,
|
||||
.cl_filter_item .cl_select {
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
background: #fff url(/image/web/select_arrow.svg) no-repeat 95% 55%/18px auto;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
padding-right: 28px;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.cl_filter_item input[type="text"] {
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.cl_filter_item input[type="text"]::placeholder {
|
||||
color: #B5BDC4;
|
||||
}
|
||||
|
||||
/* 날짜 필터 */
|
||||
.cl_date_item input[type="date"] {
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.cl_date_sep {
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
/* 날짜 단축 버튼 */
|
||||
.cl_quick_btns {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.cl_quick_btns button {
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #D5D8DC;
|
||||
border-radius: 4px;
|
||||
background: #FAFBFC;
|
||||
color: #555;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.cl_quick_btns button:hover {
|
||||
background: #3985EA;
|
||||
color: #fff;
|
||||
border-color: #3985EA;
|
||||
}
|
||||
|
||||
/* 조회 버튼 */
|
||||
.cl_search_btn {
|
||||
height: 32px;
|
||||
padding: 0 20px;
|
||||
background: #3985EA;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.cl_search_btn:hover {
|
||||
background: #2D6CC0;
|
||||
}
|
||||
|
||||
/* ---- 통계 요약 바 ---- */
|
||||
.cl_stats_bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: #F7F9FC;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
padding: 10px 16px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cl_stats_bar strong {
|
||||
color: #3985EA;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cl_stats_sep {
|
||||
color: #D5D8DC;
|
||||
}
|
||||
|
||||
.cl_stats_detail {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.cl_stats_detail strong {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ---- ag-Grid 영역 ---- */
|
||||
.cl_grid_box {
|
||||
width: 100%;
|
||||
height: calc(100% - 230px);
|
||||
min-height: 300px;
|
||||
background: #fff;
|
||||
border: solid 1px #E9ECF0;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ---- 메모 팝업 ---- */
|
||||
.cl_memo_overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cl_memo_popup {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
width: 480px;
|
||||
max-width: 90%;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cl_memo_header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #E9ECF0;
|
||||
}
|
||||
|
||||
.cl_memo_header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.cl_memo_record {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.cl_memo_close {
|
||||
margin-left: auto;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 22px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.cl_memo_close:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.cl_memo_body {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.cl_memo_body textarea {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
line-height: 1.6;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cl_memo_body textarea:focus {
|
||||
border-color: #3985EA;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(57, 133, 234, 0.12);
|
||||
}
|
||||
|
||||
.cl_memo_footer {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px 20px 16px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.cl_memo_save_btn {
|
||||
height: 34px;
|
||||
padding: 0 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: #3985EA;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cl_memo_save_btn:hover {
|
||||
background: #2D6CC0;
|
||||
}
|
||||
|
||||
.cl_memo_cancel_btn {
|
||||
height: 34px;
|
||||
padding: 0 16px;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cl_memo_cancel_btn:hover {
|
||||
background: #F7F8FA;
|
||||
}
|
||||
|
||||
/* ---- 그리드 내 버튼/뱃지 ---- */
|
||||
.cl_play_btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 1px solid #3985EA;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
color: #3985EA;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.cl_play_btn:hover {
|
||||
background: #3985EA;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.cl_incall_badge {
|
||||
display: inline-block;
|
||||
padding: 1px 6px;
|
||||
margin: 1px 2px;
|
||||
background: #EEF3FB;
|
||||
color: #3366AA;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ---- 반응형 ---- */
|
||||
@media screen and (max-width: 1500px) {
|
||||
.cl_filter_row {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cl_filter_item label {
|
||||
min-width: 45px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cl_filter_item select,
|
||||
.cl_filter_item .cl_select {
|
||||
min-width: 80px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cl_filter_item input[type="text"] {
|
||||
width: 120px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cl_quick_btns button {
|
||||
padding: 0 7px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
285
src/main/resources/static/css/web/sms_template.css
Normal file
285
src/main/resources/static/css/web/sms_template.css
Normal file
@@ -0,0 +1,285 @@
|
||||
/* ============================================
|
||||
문자 상용구 관리 - 좌측 목록 / 우측 상세
|
||||
============================================ */
|
||||
|
||||
/* ---- 2패널 컨테이너 ---- */
|
||||
.sms_two_panel {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
height: calc(100% - 60px);
|
||||
min-height: 400px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* ---- 좌측 목록 패널 ---- */
|
||||
.sms_left_panel {
|
||||
width: 45%;
|
||||
min-width: 360px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 검색 영역 */
|
||||
.sms_search_area {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #E9ECF0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sms_search_box {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.sms_search_box img {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: 8px;
|
||||
z-index: 1;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.sms_search_box input {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
padding: 0 10px 0 32px;
|
||||
font-size: 13px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sms_search_box input::placeholder {
|
||||
color: #B5BDC4;
|
||||
}
|
||||
|
||||
.sms_search_btn {
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
background: #3985EA;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sms_search_btn:hover {
|
||||
background: #2D6CC0;
|
||||
}
|
||||
|
||||
.sms_new_btn {
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
background: #fff;
|
||||
color: #3985EA;
|
||||
border: 1px solid #3985EA;
|
||||
border-radius: 5px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sms_new_btn:hover {
|
||||
background: #3985EA;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ag-Grid 영역 */
|
||||
.sms_grid_box {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ---- 우측 상세 패널 ---- */
|
||||
.sms_right_panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 빈 상태 */
|
||||
.sms_empty_state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sms_empty_state p {
|
||||
text-align: center;
|
||||
color: #B5BDC4;
|
||||
font-size: 14px;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
/* 상세 폼 */
|
||||
.sms_detail_form {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sms_detail_title_bar {
|
||||
padding: 14px 20px;
|
||||
border-bottom: 1px solid #E9ECF0;
|
||||
}
|
||||
|
||||
.sms_detail_title_bar h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.sms_form_row {
|
||||
padding: 0 20px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.sms_form_row label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.sms_form_row input[type="text"] {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sms_form_row textarea {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
min-height: 160px;
|
||||
line-height: 1.6;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sms_form_row input:focus,
|
||||
.sms_form_row textarea:focus {
|
||||
border-color: #3985EA;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(57, 133, 234, 0.12);
|
||||
}
|
||||
|
||||
.byte_info {
|
||||
text-align: right;
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.type_badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
margin-left: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.type_badge.sms {
|
||||
background: #E8F5E9;
|
||||
color: #2E7D32;
|
||||
}
|
||||
|
||||
.type_badge.lms {
|
||||
background: #FFF3E0;
|
||||
color: #E65100;
|
||||
}
|
||||
|
||||
.sms_info_row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 10px 20px !important;
|
||||
margin-top: 10px !important;
|
||||
border-top: 1px solid #F0F1F3;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.sms_info_row strong {
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.sms_btn_group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 14px 20px;
|
||||
border-top: 1px solid #E9ECF0;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.sms_save_btn {
|
||||
height: 36px;
|
||||
padding: 0 24px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: #3985EA;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sms_save_btn:hover {
|
||||
background: #2D6CC0;
|
||||
}
|
||||
|
||||
.sms_delete_btn {
|
||||
height: 36px;
|
||||
padding: 0 18px;
|
||||
border: 1px solid #FF2222;
|
||||
border-radius: 5px;
|
||||
background: transparent;
|
||||
color: #FF2222;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sms_delete_btn:hover {
|
||||
background: #FF2222;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sms_cancel_btn {
|
||||
height: 36px;
|
||||
padding: 0 18px;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sms_cancel_btn:hover {
|
||||
background: #F7F8FA;
|
||||
}
|
||||
427
src/main/resources/static/js/web/callLog/callLogSelectList.js
Normal file
427
src/main/resources/static/js/web/callLog/callLogSelectList.js
Normal file
@@ -0,0 +1,427 @@
|
||||
/**
|
||||
* 통화 로그 관리 - ag-Grid
|
||||
*/
|
||||
$(document).ready(function () {
|
||||
fn_initGrid();
|
||||
fn_init();
|
||||
fn_setToday();
|
||||
fn_loadCallLogList();
|
||||
});
|
||||
|
||||
/* ============================================
|
||||
전역 변수
|
||||
============================================ */
|
||||
var gridApi = null;
|
||||
|
||||
/* ARS 메뉴 번호 → 텍스트 매핑 */
|
||||
var arsMenuMap = {
|
||||
'1': '상담',
|
||||
'2': '예약',
|
||||
'3': '위치안내'
|
||||
};
|
||||
|
||||
/* ============================================
|
||||
ag-Grid 초기화
|
||||
============================================ */
|
||||
function fn_initGrid() {
|
||||
var columnDefs = [
|
||||
{
|
||||
headerName: 'No',
|
||||
valueGetter: function (params) {
|
||||
return params.node.rowIndex + 1;
|
||||
},
|
||||
width: 60,
|
||||
maxWidth: 60,
|
||||
cellStyle: { textAlign: 'center' },
|
||||
suppressSizeToFit: true
|
||||
},
|
||||
{
|
||||
headerName: 'RECORD NO',
|
||||
field: 'recordNo',
|
||||
width: 100,
|
||||
maxWidth: 110,
|
||||
cellStyle: { textAlign: 'center' }
|
||||
},
|
||||
{
|
||||
headerName: '구분1',
|
||||
field: 'menuText',
|
||||
width: 100,
|
||||
maxWidth: 120,
|
||||
cellStyle: { textAlign: 'center' },
|
||||
valueGetter: function (params) {
|
||||
var d = params.data;
|
||||
if (!d) return '';
|
||||
if (d.bound === 'OUT') return 'OUT';
|
||||
if (d.callbackCid) return 'Call Back';
|
||||
return arsMenuMap[d.ring] || '';
|
||||
},
|
||||
cellRenderer: function (params) {
|
||||
if (!params.data) return '';
|
||||
if (params.data.bound === 'OUT') {
|
||||
return '<span style="color:#0000ff;font-weight:600">OUT</span>';
|
||||
}
|
||||
if (params.data.callbackCid) {
|
||||
return 'Call Back<br><small>' + params.data.callbackCid + '</small>';
|
||||
}
|
||||
return params.value || '';
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: '구분2',
|
||||
field: 'cType1',
|
||||
width: 65,
|
||||
maxWidth: 80,
|
||||
cellStyle: { textAlign: 'center' }
|
||||
},
|
||||
{
|
||||
headerName: '구분3',
|
||||
field: 'cType2',
|
||||
width: 80,
|
||||
maxWidth: 100,
|
||||
cellStyle: { textAlign: 'center' }
|
||||
},
|
||||
{
|
||||
headerName: '고객명',
|
||||
field: 'memberName',
|
||||
width: 90,
|
||||
maxWidth: 120,
|
||||
cellStyle: { textAlign: 'center' }
|
||||
},
|
||||
{
|
||||
headerName: '전화번호',
|
||||
field: 'cid',
|
||||
width: 120,
|
||||
maxWidth: 140,
|
||||
cellStyle: { textAlign: 'center' },
|
||||
valueFormatter: function (params) {
|
||||
return fn_formatPhone(params.value);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: '전화 온 시간',
|
||||
field: 'regDate',
|
||||
width: 145,
|
||||
maxWidth: 160,
|
||||
cellStyle: { textAlign: 'center' }
|
||||
},
|
||||
{
|
||||
headerName: '통화 시작',
|
||||
field: 'linkDate',
|
||||
width: 145,
|
||||
maxWidth: 160,
|
||||
cellStyle: { textAlign: 'center' }
|
||||
},
|
||||
{
|
||||
headerName: '통화 종료',
|
||||
valueGetter: function (params) {
|
||||
if (!params.data) return '';
|
||||
return params.data.linkEndDate || params.data.bendDate || '';
|
||||
},
|
||||
width: 145,
|
||||
maxWidth: 160,
|
||||
cellStyle: { textAlign: 'center' }
|
||||
},
|
||||
{
|
||||
headerName: '상담 전화번호',
|
||||
field: 'linkCid',
|
||||
width: 110,
|
||||
maxWidth: 130,
|
||||
cellStyle: { textAlign: 'center' }
|
||||
},
|
||||
{
|
||||
headerName: '통화파일',
|
||||
field: 'recordNo',
|
||||
width: 80,
|
||||
maxWidth: 90,
|
||||
cellStyle: { textAlign: 'center' },
|
||||
cellRenderer: function (params) {
|
||||
if (!params.data || !params.data.recordNo || !params.data.linkDate) return '';
|
||||
var btn = '<button type="button" class="cl_play_btn" onclick="fn_playRecord(\'' + params.data.recordNo + '\')">▶</button>';
|
||||
return btn;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: 'In Call',
|
||||
field: 'incallSel',
|
||||
width: 150,
|
||||
minWidth: 100,
|
||||
cellStyle: { textAlign: 'left' },
|
||||
cellRenderer: function (params) {
|
||||
var val = params.value;
|
||||
if (!val) return '';
|
||||
// incallSel은 '|=|값1|=||=|값2|=|' 형식
|
||||
var items = val.split('|=|').filter(function (v) { return v.trim() !== ''; });
|
||||
if (items.length === 0) return '';
|
||||
return items.map(function (item) {
|
||||
return '<span class="cl_incall_badge">' + item + '</span>';
|
||||
}).join(' ');
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: '메모',
|
||||
field: 'callMsg',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
cellStyle: { textAlign: 'left', cursor: 'pointer' },
|
||||
cellRenderer: function (params) {
|
||||
var msg = params.value || '';
|
||||
// HTML 태그 제거하여 표시
|
||||
var plainText = msg.replace(/<[^>]+>/g, '').trim();
|
||||
if (plainText.length > 30) {
|
||||
plainText = plainText.substring(0, 30) + '...';
|
||||
}
|
||||
return plainText || '<span style="color:#B5BDC4">클릭하여 메모 입력</span>';
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
var gridOptions = {
|
||||
columnDefs: columnDefs,
|
||||
rowData: [],
|
||||
rowSelection: 'single',
|
||||
animateRows: true,
|
||||
headerHeight: 36,
|
||||
rowHeight: 40,
|
||||
suppressCellFocus: true,
|
||||
overlayNoRowsTemplate: '<span style="color:#B5BDC4; font-size:13px;">검색된 통화 내역이 없습니다.</span>',
|
||||
onCellClicked: function (event) {
|
||||
if (event.colDef.field === 'callMsg') {
|
||||
fn_openMemo(event.data);
|
||||
}
|
||||
},
|
||||
defaultColDef: {
|
||||
resizable: true,
|
||||
sortable: true
|
||||
}
|
||||
};
|
||||
|
||||
var gridDiv = document.querySelector('#callLogGrid');
|
||||
gridApi = new agGrid.Grid(gridDiv, gridOptions);
|
||||
gridApi = gridOptions.api;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
초기화 (이벤트 바인딩)
|
||||
============================================ */
|
||||
function fn_init() {
|
||||
$('#searchBtn').on('click', function () {
|
||||
fn_loadCallLogList();
|
||||
});
|
||||
|
||||
$('#mCid').on('keypress', function (e) {
|
||||
if (e.which === 13) fn_loadCallLogList();
|
||||
});
|
||||
|
||||
$('#sDate, #eDate').on('change', function () {
|
||||
// 날짜 변경 시 자동 검색하지 않음 (사용자가 조회 버튼 클릭)
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
오늘 날짜 설정
|
||||
============================================ */
|
||||
function fn_setToday() {
|
||||
var today = fn_getDateStr(new Date());
|
||||
$('#sDate').val(today);
|
||||
$('#eDate').val(today);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
날짜 단축 버튼
|
||||
============================================ */
|
||||
function fn_dayChk(type) {
|
||||
var now = new Date();
|
||||
var sDate, eDate;
|
||||
|
||||
switch (type) {
|
||||
case 't': // 오늘
|
||||
sDate = new Date();
|
||||
eDate = new Date();
|
||||
break;
|
||||
case 'p': // 어제
|
||||
sDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
|
||||
eDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
|
||||
break;
|
||||
case 'w': // 이번주 (월요일~오늘)
|
||||
var dayOfWeek = now.getDay();
|
||||
var diff = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
||||
sDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - diff);
|
||||
eDate = new Date();
|
||||
break;
|
||||
case 'pw': // 지난주 (월요일~일요일)
|
||||
var dayOfWeek = now.getDay();
|
||||
var diff = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
||||
var thisMonday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - diff);
|
||||
sDate = new Date(thisMonday.getFullYear(), thisMonday.getMonth(), thisMonday.getDate() - 7);
|
||||
eDate = new Date(thisMonday.getFullYear(), thisMonday.getMonth(), thisMonday.getDate() - 1);
|
||||
break;
|
||||
case 'm': // 이번달
|
||||
sDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
eDate = new Date();
|
||||
break;
|
||||
case 'pm': // 지난달
|
||||
sDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
eDate = new Date(now.getFullYear(), now.getMonth(), 0);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
$('#sDate').val(fn_getDateStr(sDate));
|
||||
$('#eDate').val(fn_getDateStr(eDate));
|
||||
fn_loadCallLogList();
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
목록 조회
|
||||
============================================ */
|
||||
function fn_loadCallLogList() {
|
||||
var param = {
|
||||
sDate: $('#sDate').val(),
|
||||
eDate: $('#eDate').val(),
|
||||
callType: $('#callType').val(),
|
||||
callType1: $('#callType1').val(),
|
||||
callType2: $('#callType2').val(),
|
||||
mCid: $('#mCid').val().replace(/[^0-9]/g, ''),
|
||||
fulldnis: '', // TODO: 매장별 수신번호 설정 필요
|
||||
menuClass: menuClass
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/callLog/getCallLogList.do',
|
||||
type: 'POST',
|
||||
data: param,
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
var rows = data.rows || [];
|
||||
gridApi.setRowData(rows);
|
||||
|
||||
if (rows.length === 0) {
|
||||
gridApi.showNoRowsOverlay();
|
||||
}
|
||||
|
||||
// 통계 업데이트
|
||||
fn_updateStats(data.stats);
|
||||
},
|
||||
error: function () {
|
||||
alert('목록 조회 중 오류가 발생하였습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
통계 업데이트
|
||||
============================================ */
|
||||
function fn_updateStats(stats) {
|
||||
if (!stats) {
|
||||
$('#statTotal, #statIn, #statOut, #statType1, #statType2, #statType11, #statType12, #statType13').text('0');
|
||||
return;
|
||||
}
|
||||
|
||||
$('#statTotal').text(fn_numberFormat(stats.totalCnt || 0));
|
||||
$('#statIn').text(fn_numberFormat(stats.inCnt || 0));
|
||||
$('#statOut').text(fn_numberFormat(stats.outCnt || 0));
|
||||
$('#statType1').text(fn_numberFormat(stats.type1Cnt || 0));
|
||||
$('#statType2').text(fn_numberFormat(stats.type2Cnt || 0));
|
||||
$('#statType11').text(fn_numberFormat(stats.type11Cnt || 0));
|
||||
$('#statType12').text(fn_numberFormat(stats.type12Cnt || 0));
|
||||
$('#statType13').text(fn_numberFormat(stats.type13Cnt || 0));
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
메모 팝업
|
||||
============================================ */
|
||||
function fn_openMemo(data) {
|
||||
if (!data || !data.recordNo) return;
|
||||
|
||||
$('#memoRecordNoVal').val(data.recordNo);
|
||||
$('#memoRecordNo').text('RECORD NO: ' + data.recordNo);
|
||||
$('#memoContent').val(data.callMsg || '');
|
||||
$('#memoOverlay').fadeIn(200);
|
||||
$('#memoContent').focus();
|
||||
}
|
||||
|
||||
function fn_closeMemo() {
|
||||
$('#memoOverlay').fadeOut(200);
|
||||
}
|
||||
|
||||
function fn_saveMemo() {
|
||||
var recordNo = $('#memoRecordNoVal').val();
|
||||
var callMsg = $.trim($('#memoContent').val());
|
||||
|
||||
if (!recordNo) return;
|
||||
|
||||
var param = {
|
||||
recordNo: recordNo,
|
||||
callMsg: callMsg,
|
||||
menuClass: menuClass
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/callLog/saveCallMemo.do',
|
||||
type: 'POST',
|
||||
data: param,
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
alert(data.msgDesc || '저장되었습니다.');
|
||||
fn_closeMemo();
|
||||
fn_loadCallLogList();
|
||||
},
|
||||
error: function () {
|
||||
alert('메모 저장 중 오류가 발생하였습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
유틸리티 함수
|
||||
============================================ */
|
||||
function fn_getDateStr(date) {
|
||||
var y = date.getFullYear();
|
||||
var m = ('0' + (date.getMonth() + 1)).slice(-2);
|
||||
var d = ('0' + date.getDate()).slice(-2);
|
||||
return y + '-' + m + '-' + d;
|
||||
}
|
||||
|
||||
function fn_numberFormat(num) {
|
||||
if (!num && num !== 0) return '0';
|
||||
return Number(num).toLocaleString();
|
||||
}
|
||||
|
||||
function fn_formatPhone(phone) {
|
||||
if (!phone) return '';
|
||||
phone = phone.replace(/[^0-9]/g, '');
|
||||
if (phone.length === 11) {
|
||||
return phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
|
||||
} else if (phone.length === 10) {
|
||||
return phone.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3');
|
||||
} else if (phone.length === 9) {
|
||||
return phone.replace(/(\d{2})(\d{3})(\d{4})/, '$1-$2-$3');
|
||||
}
|
||||
return phone;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
녹음 파일 재생
|
||||
============================================ */
|
||||
function fn_playRecord(recordNo) {
|
||||
if (!recordNo) return;
|
||||
|
||||
$.ajax({
|
||||
url: '/callLog/getRecordFileUrl.do',
|
||||
type: 'POST',
|
||||
data: { recordNo: recordNo },
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
if (data.success && data.recordUrl) {
|
||||
window.open(data.recordUrl, '_blank', 'width=400,height=200');
|
||||
} else {
|
||||
alert('녹음 파일을 불러올 수 없습니다.');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
alert('녹음 파일 조회 중 오류가 발생하였습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
/**
|
||||
* 문자 상용구 관리 - ag-Grid
|
||||
*/
|
||||
$(document).ready(function () {
|
||||
fn_initGrid();
|
||||
fn_init();
|
||||
fn_loadTemplateList();
|
||||
});
|
||||
|
||||
/* ============================================
|
||||
전역 변수
|
||||
============================================ */
|
||||
var gridApi = null;
|
||||
var currentMode = ''; // 'new' | 'edit'
|
||||
var currentTemplateId = '';
|
||||
|
||||
/* ============================================
|
||||
ag-Grid 초기화
|
||||
============================================ */
|
||||
function fn_initGrid() {
|
||||
var columnDefs = [
|
||||
{
|
||||
headerName: 'No',
|
||||
valueGetter: function (params) {
|
||||
return params.node.rowIndex + 1;
|
||||
},
|
||||
width: 55,
|
||||
maxWidth: 55,
|
||||
cellStyle: { textAlign: 'center' },
|
||||
suppressSizeToFit: true
|
||||
},
|
||||
{
|
||||
headerName: '제목',
|
||||
field: 'title',
|
||||
flex: 2,
|
||||
minWidth: 120,
|
||||
cellStyle: { textAlign: 'left' }
|
||||
},
|
||||
{
|
||||
headerName: '등록자',
|
||||
field: 'regName',
|
||||
width: 80,
|
||||
maxWidth: 100,
|
||||
cellStyle: { textAlign: 'center' }
|
||||
},
|
||||
{
|
||||
headerName: '등록일',
|
||||
field: 'regDate',
|
||||
width: 100,
|
||||
maxWidth: 120,
|
||||
cellStyle: { textAlign: 'center' }
|
||||
}
|
||||
];
|
||||
|
||||
var gridOptions = {
|
||||
columnDefs: columnDefs,
|
||||
rowData: [],
|
||||
rowSelection: 'single',
|
||||
animateRows: true,
|
||||
headerHeight: 36,
|
||||
rowHeight: 38,
|
||||
suppressCellFocus: true,
|
||||
overlayNoRowsTemplate: '<span style="color:#B5BDC4; font-size:13px;">등록된 상용구가 없습니다.</span>',
|
||||
onRowClicked: function (event) {
|
||||
var id = event.data.muSmsTemplateId;
|
||||
if (id) fn_selectTemplate(id);
|
||||
},
|
||||
defaultColDef: {
|
||||
resizable: true,
|
||||
sortable: true
|
||||
}
|
||||
};
|
||||
|
||||
var gridDiv = document.querySelector('#smsTemplateGrid');
|
||||
gridApi = new agGrid.Grid(gridDiv, gridOptions);
|
||||
gridApi = gridOptions.api;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
초기화
|
||||
============================================ */
|
||||
function fn_init() {
|
||||
$('#searchBtn').on('click', function () {
|
||||
fn_loadTemplateList();
|
||||
});
|
||||
|
||||
$('#searchKeyword').on('keypress', function (e) {
|
||||
if (e.which === 13) fn_loadTemplateList();
|
||||
});
|
||||
|
||||
$('#newTemplateBtn').on('click', function () {
|
||||
fn_newTemplate();
|
||||
});
|
||||
|
||||
$('#saveBtn').on('click', function () {
|
||||
fn_saveTemplate();
|
||||
});
|
||||
|
||||
$('#deleteBtn').on('click', function () {
|
||||
fn_deleteTemplate();
|
||||
});
|
||||
|
||||
$('#cancelBtn').on('click', function () {
|
||||
fn_cancelEdit();
|
||||
});
|
||||
|
||||
$('#templateContent').on('input', function () {
|
||||
fn_updateByteCount();
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
목록 조회
|
||||
============================================ */
|
||||
function fn_loadTemplateList() {
|
||||
var param = {
|
||||
searchKeyword: $('#searchKeyword').val(),
|
||||
menuClass: menuClass
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/smsTemplate/getSmsTemplateList.do',
|
||||
type: 'POST',
|
||||
data: param,
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
var rows = data.rows || [];
|
||||
gridApi.setRowData(rows);
|
||||
|
||||
if (rows.length === 0) {
|
||||
gridApi.showNoRowsOverlay();
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
alert('목록 조회 중 오류가 발생하였습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
상세 조회
|
||||
============================================ */
|
||||
function fn_selectTemplate(templateId) {
|
||||
var param = {
|
||||
muSmsTemplateId: templateId,
|
||||
menuClass: menuClass
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/smsTemplate/getSmsTemplate.do',
|
||||
type: 'POST',
|
||||
data: param,
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
if (data.data) {
|
||||
fn_showDetail(data.data);
|
||||
} else {
|
||||
alert(data.msgDesc || '상세 조회에 실패하였습니다.');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
alert('상세 조회 중 오류가 발생하였습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fn_showDetail(detail) {
|
||||
currentMode = 'edit';
|
||||
currentTemplateId = detail.muSmsTemplateId;
|
||||
|
||||
$('#detailTitle').text('상용구 수정');
|
||||
$('#muSmsTemplateId').val(detail.muSmsTemplateId);
|
||||
$('#templateTitle').val(detail.title || '');
|
||||
$('#templateContent').val(detail.content || '');
|
||||
$('#regName').text(detail.regName || '-');
|
||||
$('#regDate').text(detail.regDate || '-');
|
||||
$('#modDate').text(detail.modDate || '-');
|
||||
|
||||
$('#infoRow').show();
|
||||
$('#deleteBtn').show();
|
||||
$('#emptyState').hide();
|
||||
$('#detailForm').show();
|
||||
|
||||
fn_updateByteCount();
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
새 상용구
|
||||
============================================ */
|
||||
function fn_newTemplate() {
|
||||
currentMode = 'new';
|
||||
currentTemplateId = '';
|
||||
|
||||
$('#detailTitle').text('상용구 등록');
|
||||
$('#muSmsTemplateId').val('');
|
||||
$('#templateTitle').val('');
|
||||
$('#templateContent').val('');
|
||||
|
||||
$('#infoRow').hide();
|
||||
$('#deleteBtn').hide();
|
||||
$('#emptyState').hide();
|
||||
$('#detailForm').show();
|
||||
|
||||
fn_updateByteCount();
|
||||
|
||||
gridApi.deselectAll();
|
||||
$('#templateTitle').focus();
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
취소
|
||||
============================================ */
|
||||
function fn_cancelEdit() {
|
||||
currentMode = '';
|
||||
currentTemplateId = '';
|
||||
|
||||
$('#detailForm').hide();
|
||||
$('#emptyState').show();
|
||||
|
||||
gridApi.deselectAll();
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
저장 (등록/수정)
|
||||
============================================ */
|
||||
function fn_saveTemplate() {
|
||||
var title = $.trim($('#templateTitle').val());
|
||||
var content = $.trim($('#templateContent').val());
|
||||
|
||||
if (!title) {
|
||||
alert('제목을 입력하세요.');
|
||||
$('#templateTitle').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
alert('내용을 입력하세요.');
|
||||
$('#templateContent').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
var url = '';
|
||||
var param = {
|
||||
title: title,
|
||||
content: content,
|
||||
menuClass: menuClass
|
||||
};
|
||||
|
||||
if (currentMode === 'new') {
|
||||
url = '/smsTemplate/putSmsTemplate.do';
|
||||
} else if (currentMode === 'edit') {
|
||||
url = '/smsTemplate/modSmsTemplate.do';
|
||||
param.muSmsTemplateId = currentTemplateId;
|
||||
}
|
||||
|
||||
if (!url) return;
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: param,
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
alert(data.msgDesc || '처리되었습니다.');
|
||||
fn_cancelEdit();
|
||||
fn_loadTemplateList();
|
||||
},
|
||||
error: function () {
|
||||
alert('저장 중 오류가 발생하였습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
삭제
|
||||
============================================ */
|
||||
function fn_deleteTemplate() {
|
||||
if (!currentTemplateId) return;
|
||||
if (!confirm('이 상용구를 삭제하시겠습니까?')) return;
|
||||
|
||||
var param = {
|
||||
muSmsTemplateId: currentTemplateId,
|
||||
menuClass: menuClass
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/smsTemplate/delSmsTemplate.do',
|
||||
type: 'POST',
|
||||
data: param,
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
alert(data.msgDesc || '삭제되었습니다.');
|
||||
fn_cancelEdit();
|
||||
fn_loadTemplateList();
|
||||
},
|
||||
error: function () {
|
||||
alert('삭제 중 오류가 발생하였습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
바이트 카운트
|
||||
============================================ */
|
||||
function fn_updateByteCount() {
|
||||
var content = $('#templateContent').val() || '';
|
||||
var byteLen = fn_getByteLength(content);
|
||||
|
||||
$('#byteCount').text(byteLen);
|
||||
|
||||
var $badge = $('#typeBadge');
|
||||
if (byteLen > 90) {
|
||||
$badge.text('LMS').removeClass('sms').addClass('lms');
|
||||
} else {
|
||||
$badge.text('SMS').removeClass('lms').addClass('sms');
|
||||
}
|
||||
}
|
||||
|
||||
function fn_getByteLength(str) {
|
||||
var byte = 0;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
if (str.charCodeAt(i) <= 0x7F) {
|
||||
byte += 1;
|
||||
} else {
|
||||
byte += 2;
|
||||
}
|
||||
}
|
||||
return byte;
|
||||
}
|
||||
126
src/main/resources/templates/web/callLog/callLogSelectList.html
Normal file
126
src/main/resources/templates/web/callLog/callLogSelectList.html
Normal file
@@ -0,0 +1,126 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{/web/layout/homeLayout}">
|
||||
<th:block layout:fragment="layout_css">
|
||||
<link rel="stylesheet" href="/css/web/webFeedbackSelectList.css">
|
||||
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
|
||||
<link rel="stylesheet" href="/css/web/call_log.css">
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_top_script">
|
||||
<script>
|
||||
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
|
||||
let selectUseYn = "[[${selectUseYn}]]" == "" ? "N" : "[[${selectUseYn}]]";
|
||||
let insertUseYn = "[[${insertUseYn}]]" == "" ? "N" : "[[${insertUseYn}]]";
|
||||
let updateUseYn = "[[${updateUseYn}]]" == "" ? "N" : "[[${updateUseYn}]]";
|
||||
let deleteUseYn = "[[${deleteUseYn}]]" == "" ? "N" : "[[${deleteUseYn}]]";
|
||||
let downloadUseYn = "[[${downloadUseYn}]]" == "" ? "N" : "[[${downloadUseYn}]]";
|
||||
</script>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_content">
|
||||
<!-- 센터쪽 -->
|
||||
<div class="center_box">
|
||||
<p class="page_title">통화 로그 관리</p>
|
||||
|
||||
<!-- 검색 필터 영역 -->
|
||||
<div class="cl_filter_wrap">
|
||||
<!-- 1행: 구분 필터 -->
|
||||
<div class="cl_filter_row">
|
||||
<div class="cl_filter_item">
|
||||
<label>구분1</label>
|
||||
<select id="callType" class="cl_select">
|
||||
<option value="">전체</option>
|
||||
<option value="1">상담</option>
|
||||
<option value="2">예약</option>
|
||||
<option value="3">위치안내</option>
|
||||
<option value="callback">Call Back</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="cl_filter_item">
|
||||
<label>구분2</label>
|
||||
<select id="callType1" class="cl_select">
|
||||
<option value="">전체</option>
|
||||
<option value="초진">초진</option>
|
||||
<option value="재진">재진</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="cl_filter_item">
|
||||
<label>구분3</label>
|
||||
<select id="callType2" class="cl_select">
|
||||
<option value="">전체</option>
|
||||
<option value="상담">상담</option>
|
||||
<option value="상담후예약">상담후예약</option>
|
||||
<option value="회차예약">회차예약</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="cl_filter_item">
|
||||
<label>고객전화번호</label>
|
||||
<input type="text" id="mCid" placeholder="전화번호 입력" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2행: 날짜 필터 -->
|
||||
<div class="cl_filter_row">
|
||||
<div class="cl_filter_item cl_date_item">
|
||||
<label>검색일자</label>
|
||||
<input type="date" id="sDate" />
|
||||
<span class="cl_date_sep">~</span>
|
||||
<input type="date" id="eDate" />
|
||||
</div>
|
||||
<div class="cl_quick_btns">
|
||||
<button type="button" onclick="fn_dayChk('t')">오늘</button>
|
||||
<button type="button" onclick="fn_dayChk('p')">어제</button>
|
||||
<button type="button" onclick="fn_dayChk('w')">이번주</button>
|
||||
<button type="button" onclick="fn_dayChk('pw')">지난주</button>
|
||||
<button type="button" onclick="fn_dayChk('m')">이번달</button>
|
||||
<button type="button" onclick="fn_dayChk('pm')">지난달</button>
|
||||
</div>
|
||||
<button type="button" id="searchBtn" class="cl_search_btn">조회</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 통계 요약 바 -->
|
||||
<div class="cl_stats_bar">
|
||||
<span>전체: <strong id="statTotal">0</strong></span>
|
||||
<span class="cl_stats_sep">|</span>
|
||||
<span>IN: <strong id="statIn">0</strong></span>
|
||||
<span class="cl_stats_detail">[초진: <strong id="statType1">0</strong> / 재진: <strong
|
||||
id="statType2">0</strong>]</span>
|
||||
<span class="cl_stats_detail">[상담: <strong id="statType11">0</strong> / 상담후예약: <strong
|
||||
id="statType12">0</strong> / 회차예약: <strong id="statType13">0</strong>]</span>
|
||||
<span class="cl_stats_sep">|</span>
|
||||
<span>OUT: <strong id="statOut">0</strong></span>
|
||||
</div>
|
||||
|
||||
<!-- ag-Grid 테이블 -->
|
||||
<div id="callLogGrid" class="cl_grid_box ag-theme-balham"></div>
|
||||
|
||||
<!-- 메모 팝업 -->
|
||||
<div class="cl_memo_overlay" id="memoOverlay" style="display:none;">
|
||||
<div class="cl_memo_popup">
|
||||
<div class="cl_memo_header">
|
||||
<h3>통화 메모</h3>
|
||||
<span class="cl_memo_record" id="memoRecordNo"></span>
|
||||
<button type="button" class="cl_memo_close" onclick="fn_closeMemo()">×</button>
|
||||
</div>
|
||||
<div class="cl_memo_body">
|
||||
<input type="hidden" id="memoRecordNoVal" />
|
||||
<textarea id="memoContent" placeholder="메모를 입력하세요" rows="6"></textarea>
|
||||
</div>
|
||||
<div class="cl_memo_footer">
|
||||
<button type="button" class="cl_memo_save_btn" onclick="fn_saveMemo()">저장</button>
|
||||
<button type="button" class="cl_memo_cancel_btn" onclick="fn_closeMemo()">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="callLogForm" onsubmit="return false;"></form>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_popup">
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_script">
|
||||
<script src="/js/web/ag-grid-community-29.3.5.min.js"></script>
|
||||
<script src="/js/web/callLog/callLogSelectList.js"></script>
|
||||
</th:block>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{/web/layout/homeLayout}">
|
||||
<th:block layout:fragment="layout_css">
|
||||
<link rel="stylesheet" href="/css/web/webFeedbackSelectList.css">
|
||||
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
|
||||
<link rel="stylesheet" href="/css/web/sms_template.css">
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_top_script">
|
||||
<script>
|
||||
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
|
||||
let selectUseYn = "[[${selectUseYn}]]" == "" ? "N" : "[[${selectUseYn}]]";
|
||||
let insertUseYn = "[[${insertUseYn}]]" == "" ? "N" : "[[${insertUseYn}]]";
|
||||
let updateUseYn = "[[${updateUseYn}]]" == "" ? "N" : "[[${updateUseYn}]]";
|
||||
let deleteUseYn = "[[${deleteUseYn}]]" == "" ? "N" : "[[${deleteUseYn}]]";
|
||||
let downloadUseYn = "[[${downloadUseYn}]]" == "" ? "N" : "[[${downloadUseYn}]]";
|
||||
</script>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_content">
|
||||
<!-- 센터쪽 -->
|
||||
<div class="center_box">
|
||||
<p class="page_title">문자 상용구 관리</p>
|
||||
|
||||
<!-- 2패널 컨테이너 -->
|
||||
<div class="sms_two_panel">
|
||||
<!-- 좌측: 목록 -->
|
||||
<div class="sms_left_panel">
|
||||
<div class="sms_search_area">
|
||||
<div class="sms_search_box">
|
||||
<img src="/image/web/search_G.svg" alt="search" />
|
||||
<input type="text" id="searchKeyword" placeholder="제목 또는 내용" />
|
||||
</div>
|
||||
<button id="searchBtn" class="sms_search_btn">조회</button>
|
||||
<button type="button" id="newTemplateBtn" class="sms_new_btn">등록</button>
|
||||
</div>
|
||||
|
||||
<div id="smsTemplateGrid" class="sms_grid_box ag-theme-balham"></div>
|
||||
</div>
|
||||
|
||||
<!-- 우측: 상세/편집 -->
|
||||
<div class="sms_right_panel">
|
||||
<!-- 빈 상태 -->
|
||||
<div class="sms_empty_state" id="emptyState">
|
||||
<p>좌측 목록에서 상용구를 선택하거나<br />"등록" 버튼을 클릭하세요.</p>
|
||||
</div>
|
||||
|
||||
<!-- 상세 폼 -->
|
||||
<div class="sms_detail_form" id="detailForm" style="display:none;">
|
||||
<div class="sms_detail_title_bar">
|
||||
<h3 id="detailTitle">상용구 등록</h3>
|
||||
</div>
|
||||
<input type="hidden" id="muSmsTemplateId" />
|
||||
<div class="sms_form_row">
|
||||
<label for="templateTitle">제목</label>
|
||||
<input type="text" id="templateTitle" placeholder="상용구 제목을 입력하세요" maxlength="100" />
|
||||
</div>
|
||||
<div class="sms_form_row">
|
||||
<label for="templateContent">내용</label>
|
||||
<textarea id="templateContent" placeholder="상용구 내용을 입력하세요" rows="8"></textarea>
|
||||
<div class="byte_info">
|
||||
<span><span id="byteCount">0</span> byte</span>
|
||||
<span class="type_badge sms" id="typeBadge">SMS</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sms_form_row sms_info_row" id="infoRow" style="display:none;">
|
||||
<span>등록자: <strong id="regName">-</strong></span>
|
||||
<span>등록일: <strong id="regDate">-</strong></span>
|
||||
<span>수정일: <strong id="modDate">-</strong></span>
|
||||
</div>
|
||||
<div class="sms_btn_group">
|
||||
<button type="button" id="saveBtn" class="sms_save_btn">저장</button>
|
||||
<button type="button" id="deleteBtn" class="sms_delete_btn" style="display:none;">삭제</button>
|
||||
<button type="button" id="cancelBtn" class="sms_cancel_btn">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="smsTemplateForm" onsubmit="return false;"></form>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_popup">
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_script">
|
||||
<script src="/js/web/ag-grid-community-29.3.5.min.js"></script>
|
||||
<script src="/js/web/smsTemplate/smsTemplateSelectList.js"></script>
|
||||
</th:block>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user