feat: 발주기관 건설현장 추가 시 기업 사용자 알림 기능 추가 건

main
thkim 2026-02-09 18:30:41 +09:00
parent 91ad7725d4
commit a2ef9363ec
15 changed files with 993 additions and 33 deletions

View File

@ -24,6 +24,8 @@ import geoinfo.drilling.input.service.DrillingInputMapper;
import geoinfo.drilling.input.service.DrillingInputService;
import geoinfo.drilling.inquiry.service.DrillingInquiryService;
import geoinfo.main.login.service.LoginMapper;
import geoinfo.ntfc.service.NotificationService;
import geoinfo.ntfc.service.NotificationVO;
import geoinfo.regi.common.service.CommonService;
import geoinfo.regi.projectList.service.ProjectListService;
import geoinfo.util.MyUtil;
@ -49,6 +51,8 @@ public class DrillingInputServiceImpl implements DrillingInputService {
@Resource(name = "projectListService")
private ProjectListService projectListService;
@Resource(name = "notificationService")
private NotificationService notificationService;
/**
@ -123,24 +127,63 @@ public class DrillingInputServiceImpl implements DrillingInputService {
JSONObject tempConstructSiteInfo = drillingInquiryService.drillingInquiryOneItem(request, params);
// JSONObject를 HashMap으로 변환
ArrayList<EgovMap> arrayList = (ArrayList<EgovMap>) MyUtil.JSONObjectToHashMap( tempConstructSiteInfo ).get("datas");
HashMap<String, Object> oldTempConstructSiteInfo = new HashMap<String, Object>(arrayList.get(0));
// 기업사용자 입력 진행 여부: !NULL -> 입력 전, NULL -> 입력 중(삭제불가)
EgovMap constCompanyProjectWriting = drillingInputMapper.selectConstructCompanyProjectWriting(oldTempConstructSiteInfo);
if (!"0".equals(oldTempConstructSiteInfo.get("projectStateCode")) && constCompanyProjectWriting == null) { // 5-1)
if( arrayList.size() == 0) {
throw new Exception("이미 삭제가 완료된 건설현장입니다.");
}
HashMap<String, Object> oldTempConstructSiteInfo = new HashMap<String, Object>(arrayList.get(0));
String projectCode = MyUtil.getStringFromObject( oldTempConstructSiteInfo.get("projectCode") );
if (!"0".equals(oldTempConstructSiteInfo.get("projectStateCode")) && isInputInProgress(oldTempConstructSiteInfo)) { // 5-1)
params.put("v_RetCode", 11);
params.put("v_RetMsg", "해당 프로젝트는 기업 사용자가 입력을 시작하였으므로 삭제가 불가합니다.");
params.put("v_RetMsg", "해당 프로젝트는 기업 사용자가 입력을 시작하였으므로 삭제가 불가합니다.\n프로젝트 코드: [" + projectCode + "]");
return params;
} else { // 5-2)
deleteTempMetaInfo(oldTempConstructSiteInfo);
deleteTempProjectInfo(oldTempConstructSiteInfo);
oldTempConstructSiteInfo.put("PROJECT_STATE_CODE", "0");
updateTempConstructSiteInfoSetPROJECT_CODE_NULL(oldTempConstructSiteInfo);
params = drillingInputMapper.spDelTblCsi(params);
drillingInputMapper.spDelTblCsi(params);
// 기업사용자 알림내역에 추가한다.
deleteNotification(request, response, params, oldTempConstructSiteInfo, projectCode);
}
return params;
}
/**
* .
* @param request
* @param response
* @param params
* @param oldTempConstructSiteInfo
* @param projectCode
* @throws Exception
*/
private void deleteNotification(HttpServletRequest request, HttpServletResponse response, HashMap<String, Object> params, HashMap<String, Object> oldTempConstructSiteInfo, String projectCode) throws Exception {
NotificationVO vo = new NotificationVO();
// [알림 구분 및 내용 설정]
String projectName = MyUtil.getStringFromObject(oldTempConstructSiteInfo.get("constName")); // 사업명 추출
vo.setNtfcSeCode("PRJ_DEL"); // 알림구분코드: 신규 프로젝트 등록
vo.setNtfcCn("[" + projectName + "] 건설현장 프로젝트 배정이 취소되었습니다. 발주기관 사용자에 의해 삭제되었습니다.");
// [이동 파라미터 설정] JSON 형태로 저장하여 나중에 스크립트에서 활용
String pcode = MyUtil.getStringFromObject(projectCode);
vo.setLinkParamCn("{\"PROJECT_CODE\":\"" + pcode + "\"}");
// [이동 경로 및 방식 설정]
// 클릭 시 이동할 URL (예: 프로젝트 상세조회 페이지)
vo.setLinkUrl("/meta_info.do?REPORT_TYPE=CH&PROJECT_CODE=" + pcode);
vo.setLinkMthdCode("GET"); // 이동 방식 (GET/POST)
params.put("constUserId", oldTempConstructSiteInfo.get("constUserid"));
insertNotification(request, response, params, vo);
}
@Override
public List<EgovMap> selectConstructCompanyList(HashMap<String, Object> params) throws Exception {
List<EgovMap> list = new ArrayList<EgovMap>();
@ -347,7 +390,7 @@ public class DrillingInputServiceImpl implements DrillingInputService {
// JSONObject를 HashMap으로 변환
ArrayList<EgovMap> arrayList = (ArrayList<EgovMap>) MyUtil.JSONObjectToHashMap( tempConstructSiteInfo ).get("datas");
HashMap<String, Object> oldTempConstructSiteInfo = new HashMap<String, Object>(arrayList.get(0));
String projectCode = MyUtil.getStringFromObject( oldTempConstructSiteInfo.get("projectCode") );
HashMap<String, Object> findConstCompanyCodeByConstCompanyNameParams = getOrganizationUserGlGmGsGfCodes(userId);
@ -400,6 +443,8 @@ public class DrillingInputServiceImpl implements DrillingInputService {
deleteTempMetaInfo(oldTempConstructSiteInfo);
deleteTempProjectInfo(oldTempConstructSiteInfo);
updateTempConstructSiteInfoSetPROJECT_CODE_NULL(oldTempConstructSiteInfo);
// 프로젝트 삭제를 기업사용자의 알림내역에 추가한다.
deleteNotification(request, response, params, oldTempConstructSiteInfo, projectCode);
}
drillingInputMapper.spUdtTblCsi(spUdtTblCsiParams);
} else { // !"".equals(encryptId)
@ -408,9 +453,9 @@ public class DrillingInputServiceImpl implements DrillingInputService {
String constUserId = addConstUserid;
params.put("constUserId", constUserId);
tbl = drillingInputMapper.getItemByCid( params );
if (tbl.get("projectCode") != null && !"".equals(tbl.get("projectCode"))) { // PROJECT_CODE가 존재하면 입력중인 프로젝트.
// 입력중인 프로젝트
// 입력여부 판단 필요
if (tbl.get("projectCode") != null && !"".equals(tbl.get("projectCode")) && isInputInProgress(oldTempConstructSiteInfo)) { // PROJECT_CODE가 존재하면 입력중인 프로젝트.
spUdtTblCsiParams.put("v_RetCode", 11);
spUdtTblCsiParams.put("v_RetMsg", "기업사용자 수정 불가. 이전 기업사용자가 이미 입력을 시작한 프로젝트입니다.");
} else { // 입력된 정보 없음. INSERT 처리 후 끝
params.put("constProjectCode", "");
params.put("holeNumber", -999);
@ -429,6 +474,8 @@ public class DrillingInputServiceImpl implements DrillingInputService {
} else { // 5-2)
deleteTempMetaInfo(oldTempConstructSiteInfo);
deleteTempProjectInfo(oldTempConstructSiteInfo);
// 프로젝트 삭제를 기업사용자의 알림내역에 추가한다.
deleteNotification(request, response, params, oldTempConstructSiteInfo, projectCode);
params.put("holeNumber", -999);
params.put("constProjectCode", oldTempConstructSiteInfo.get("projectCode")); // 기존에 부여된 PROJECT_CODE 유지
params.put("constUserId", addConstUserid); // 새로 선정한 건설사계정
@ -528,11 +575,65 @@ public class DrillingInputServiceImpl implements DrillingInputService {
// 2. ProjectListController의 insertMeta 호출
ModelAndView model = new ModelAndView();
model = projectListService.insertMeta(metaParams, model, request, response); // insertMeta 호출 시 발생되는 오류는 조치하기.
// 기업사용자 알림내역에 추가한다.
NotificationVO vo = new NotificationVO();
// [알림 구분 및 내용 설정]
String projectName = MyUtil.getStringFromObject(metaParams.get("PROJECT_NAME")); // 사업명 추출
vo.setNtfcSeCode("PRJ_NEW"); // 알림구분코드: 신규 프로젝트 등록
vo.setNtfcCn("[" + projectName + "] 건설현장 프로젝트가 배정되었습니다. 목록을 확인해 주세요.");
// [이동 파라미터 설정] JSON 형태로 저장하여 나중에 스크립트에서 활용
String pcode = MyUtil.getStringFromObject(metaParams.get("PROJECT_CODE"));
vo.setLinkParamCn("{\"pcode\":\"" + pcode + "\"}");
// [이동 경로 및 방식 설정]
// 클릭 시 이동할 URL (예: 프로젝트 상세조회 페이지)
vo.setLinkUrl("/meta_info.do?REPORT_TYPE=CH&PROJECT_CODE=" + pcode);
vo.setLinkMthdCode("GET"); // 이동 방식 (GET/POST)
insertNotification(request, response, params, vo);
return model;
}
private void insertNotification(HttpServletRequest request, HttpServletResponse response, HashMap<String, Object> params, NotificationVO vo) throws Exception {
// 알림 등록을 위한 데이터 설정
// [수신자 설정] params에서 기업사용자 ID(constUserId)를 가져옴
String targetUserId = MyUtil.getStringFromObject(params.get("constUserId"));
vo.setRecvUserId(targetUserId);
// [등록자 설정] 현재 세션의 발주기관 사용자 ID
String rgtrId = MyUtil.getStringFromObject(request.getSession().getAttribute("USERID"));
if (rgtrId != null && targetUserId != null) {
// [등록자 설정 및 저장]
vo.setRgtrId(rgtrId);
try {
// 실제 DB Insert 실행
notificationService.insertNotification(vo);
// 결과 로그 또는 메시지 처리
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("resultCode", 200);
resultMap.put("message", "기업사용자에게 알림이 전송되었습니다.");
} catch (Exception e) {
// 알림 등록 실패 시 로그 처리 (비즈니스 로직에 큰 영향이 없다면 에러를 던지지 않고 로그만 남김)
e.printStackTrace();
}
} else {
// 필수 정보 누락 시 처리
System.out.println("알림 발송 실패: 수신자 ID 또는 등록자 ID가 존재하지 않습니다.");
}
}
/**
* ,
*/
@ -548,7 +649,28 @@ public class DrillingInputServiceImpl implements DrillingInputService {
ModelAndView model = new ModelAndView();
model = projectListService.deleteAll(deleteParams, model, request, response); // deleteAll 호출 시 발생되는 오류는 조치하기.
return model;
}
/**
* , method.
* @return
* @throws SQLException
*/
private boolean isInputInProgress(HashMap<String, Object> oldTempConstructSiteInfo) throws SQLException {
oldTempConstructSiteInfo.put("PROJECT_CODE", oldTempConstructSiteInfo.get("projectCode"));
int nCnt = drillingInputMapper.selectConstructCompanyProjectByProjectCodeCnt(oldTempConstructSiteInfo);
if( nCnt == 0 ) {
return false;
}
EgovMap constCompanyProjectWriting = drillingInputMapper.selectConstructCompanyProjectWriting(oldTempConstructSiteInfo);
if (constCompanyProjectWriting == null) {
return true;
}
return false;
}
}

View File

@ -63,6 +63,8 @@ import geoinfo.drilling.input.service.DrillingInputService;
import geoinfo.main.login.service.LoginService;
import geoinfo.main.main.MainController;
import geoinfo.map.mapControl.service.MapControlService;
import geoinfo.ntfc.service.NotificationService;
import geoinfo.ntfc.service.NotificationVO;
import geoinfo.util.MobileCertificationUtil;
import geoinfo.util.MyUtil;
import geoinfo.util.ScriptUtil;
@ -88,6 +90,9 @@ public class LoginController {
@Resource(name="egovMessageSource")
EgovMessageSource egovMessageSource;
@Resource(name = "notificationService")
private NotificationService notificationService;
private static boolean loginFlag = true;
private static MultiValueMap users = new MultiValueMap();
@ -1733,6 +1738,61 @@ public class LoginController {
return mv;
}
// 마이페이지 알림내역.
@RequestMapping(value = "/mypage_ntfc.do")
public ModelAndView mypage_ntfc(ModelMap model, @RequestParam Map<String, Object> params, HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView("body/mypage/mypage_ntfc");
// 1. 세션 체크
String userId = (String) request.getSession().getAttribute("USERID");
if (userId == null) {
mv.setViewName("redirect:/index.do?cntyn=0");
return mv;
}
// 2. 페이지 번호 설정
int pageIndex = 0;
if (params.get("page") != null && !String.valueOf(params.get("page")).equals("null")) {
pageIndex = Integer.parseInt(String.valueOf(params.get("page")));
}
// 3. 알림 조회를 위한 VO 설정
NotificationVO vo = new NotificationVO();
vo.setRecvUserId(userId);
// 4. 페이징 처리를 위한 전체 개수 조회
int totalCount = notificationService.selectNotificationListCount(vo); // 전체 건수 조회 메서드 필요
// 5. 페이징 변수 계산 (기존 프로젝트의 initPageIndex 방식 활용)
// viewPage: 한 페이지에 보여줄 개수 (예: 10)
int viewPage = 10;
int lastPageIndex = (int) Math.ceil(totalCount / (double) viewPage) - 1;
if (lastPageIndex < 0) lastPageIndex = 0;
if (pageIndex > lastPageIndex) pageIndex = lastPageIndex;
// 기존 프로젝트의 공통 페이징 변수 세팅 로직 호출 (pre, next, startPage 등 계산)
initPageIndex(pageIndex, lastPageIndex);
params.put("firstRow", pageIndex * viewPage);
params.put("lastRow", (pageIndex + 1) * viewPage);
// JSP 페이징 함수를 위한 값 세팅
params.put("prePage", Integer.toString(pre));
params.put("nextPage", Integer.toString(next));
params.put("nowPage", pageIndex);
params.put("startPage", startPage);
params.put("endPage", endPage);
// 6. 알림 목록 실제 데이터 조회
// MyBatis에서 firstRow, lastRow를 처리하도록 params를 VO에 담거나 Map으로 전달
List<NotificationVO> items = notificationService.selectNotificationList(vo); // 페이징 쿼리 적용 필요
model.put("params", params);
model.put("items", items);
return mv;
}
// 마이페이지 장바구니.
@RequestMapping(value = "/mypage_cart.do")
public ModelAndView mypage_cart(ModelMap model,@RequestParam Map<String, Object> params, HttpServletRequest request, HttpServletResponse response) throws Exception {

View File

@ -500,8 +500,11 @@ public class MainController
mv.addObject("msg", "잘못된 접근입니다.");
eGovUrl = "passch.do";
}
}
else if (url.equals("mypage_cart"))
} else if (url.equals("mypage_ntfc")) {
userId = request.getSession().getAttribute("USERID").toString();
eGovUrl = "mypage_ntfc.do";
mv.setViewName("home/main.jsp?url=/body/mypage/ntfc");
} else if (url.equals("mypage_cart"))
{
userId = request.getSession().getAttribute("USERID").toString();
eGovUrl = "mypage_cart.do";

View File

@ -0,0 +1,203 @@
package geoinfo.ntfc;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import geoinfo.ntfc.service.NotificationService;
import geoinfo.ntfc.service.NotificationVO;
import geoinfo.util.MyUtil;
@Controller
public class NotificationController {
@Resource(name = "notificationService")
private NotificationService notificationService;
/**
* (Ajax)
*/
@RequestMapping(value = "/ntfc/getUnreadCount.do")
@ResponseBody
public Map<String, Object> getUnreadCount(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> resultMap = new HashMap<>();
String userId = MyUtil.getStringFromObject(request.getSession().getAttribute("USERID"));
if (userId == null) {
resultMap.put("resultCode", 401);
resultMap.put("result", "false");
resultMap.put("message", "로그인이 필요한 서비스입니다.");
return resultMap;
}
int count = notificationService.selectUnreadNotificationCount(userId);
resultMap.put("count", count);
return resultMap;
}
/**
* (Ajax)
*/
@RequestMapping(value = "/ntfc/getList.do")
@ResponseBody
public Map<String, Object> getNtfcList(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> resultMap = new HashMap<>();
String userId = MyUtil.getStringFromObject(request.getSession().getAttribute("USERID"));
if (userId == null) {
resultMap.put("resultCode", 401);
resultMap.put("result", "false");
resultMap.put("message", "로그인이 필요한 서비스입니다.");
return resultMap;
}
NotificationVO vo = new NotificationVO();
vo.setRecvUserId(userId);
List<NotificationVO> list = notificationService.selectNotificationList(vo);
resultMap.put("list", list);
return resultMap;
}
/**
*
*/
@RequestMapping(value = "/ntfc/clickAction.do")
@ResponseBody
public Map<String, Object> clickAction(HttpServletRequest request, HttpServletResponse response, @RequestParam("ntfcSn") long ntfcSn) throws Exception {
Map<String, Object> resultMap = new HashMap<>();
// 1. 읽음 상태로 업데이트
notificationService.updateNotificationConfirm(ntfcSn);
// 2. 이후 프론트엔드에서 LINK_URL 및 LINK_MTHD_CODE에 따라
// GET/POST 방식으로 이동할 수 있도록 정보를 성공 메시지와 함께 전달합니다.
resultMap.put("status", "success");
return resultMap;
}
/**
* ( )
*/
@RequestMapping(value = "/ntfc/updateBulkRead.do")
@ResponseBody
public Map<String, Object> updateBulkRead(HttpServletRequest request, @RequestParam("ntfcSns") String ntfcSns) throws Exception {
Map<String, Object> resultMap = new HashMap<>();
String userId = MyUtil.getStringFromObject(request.getSession().getAttribute("USERID"));
if (userId == null) {
resultMap.put("resultCode", 401);
resultMap.put("message", "로그인이 필요한 서비스입니다.");
return resultMap;
}
if (ntfcSns != null && !ntfcSns.isEmpty()) {
// 콤마로 구분된 SN들을 배열로 분리
String[] snArray = ntfcSns.split(",");
for (String sn : snArray) {
try {
long ntfcSn = Long.parseLong(sn.trim());
// 개별 알림 확인 처리 로직 재활용
notificationService.updateNotificationConfirm(ntfcSn);
} catch (NumberFormatException e) {
// 유효하지 않은 번호는 스킵
continue;
}
}
resultMap.put("resultCode", 200);
resultMap.put("status", "success");
resultMap.put("message", "알림이 모두 읽음 처리되었습니다.");
} else {
resultMap.put("resultCode", 400);
resultMap.put("message", "처리할 알림 번호가 없습니다.");
}
return resultMap;
}
/**
* (/)
*/
@RequestMapping(value = "/ntfc/insertNtfc.do")
@ResponseBody
public Map<String, Object> insertNtfc(HttpServletRequest request, NotificationVO vo) throws Exception {
Map<String, Object> resultMap = new HashMap<>();
String rgtrId = MyUtil.getStringFromObject(request.getSession().getAttribute("USERID"));
if (rgtrId == null) {
resultMap.put("resultCode", 401);
resultMap.put("message", "로그인이 필요한 서비스입니다.");
return resultMap;
}
// 수신자ID 및 필수 내용 체크
if (vo.getRecvUserId() == null || vo.getNtfcCn() == null) {
resultMap.put("resultCode", 400);
resultMap.put("message", "필수 파라미터가 누락되었습니다.");
return resultMap;
}
vo.setRgtrId(rgtrId); // 등록자를 현재 세션 사용자로 설정
notificationService.insertNotification(vo);
resultMap.put("resultCode", 200);
resultMap.put("message", "알림이 정상적으로 등록되었습니다.");
return resultMap;
}
/**
*
*/
@RequestMapping(value = "/ntfc/updateNtfc.do")
@ResponseBody
public Map<String, Object> updateNtfc(HttpServletRequest request, NotificationVO vo) throws Exception {
Map<String, Object> resultMap = new HashMap<>();
String userId = MyUtil.getStringFromObject(request.getSession().getAttribute("USERID"));
if (userId == null) {
resultMap.put("resultCode", 401);
return resultMap;
}
if (vo.getNtfcSn() <= 0) {
resultMap.put("resultCode", 400);
resultMap.put("message", "수정할 알림 번호가 유효하지 않습니다.");
return resultMap;
}
notificationService.updateNotification(vo);
resultMap.put("resultCode", 200);
resultMap.put("message", "알림이 수정되었습니다.");
return resultMap;
}
/**
*
*/
@RequestMapping(value = "/ntfc/deleteNtfc.do")
@ResponseBody
public Map<String, Object> deleteNtfc(HttpServletRequest request, @RequestParam("ntfcSn") long ntfcSn) throws Exception {
Map<String, Object> resultMap = new HashMap<>();
String userId = MyUtil.getStringFromObject(request.getSession().getAttribute("USERID"));
if (userId == null) {
resultMap.put("resultCode", 401);
return resultMap;
}
notificationService.deleteNotification(ntfcSn);
resultMap.put("resultCode", 200);
resultMap.put("message", "알림이 삭제되었습니다.");
return resultMap;
}
}

View File

@ -0,0 +1,68 @@
package geoinfo.ntfc.service;
import java.util.List;
import egovframework.rte.psl.dataaccess.mapper.Mapper;
/**
* (MyBatis Mapper)
* TBL_NTFC CRUD .
*/
@Mapper("notificationMapper")
public interface NotificationMapper {
/**
* .
* @param vo (ID firstRow, lastRow )
* @return
* @throws Exception
*/
List<NotificationVO> selectNotificationList(NotificationVO vo) throws Exception;
/**
* .
* .
* @param vo
* @return
* @throws Exception
*/
int selectNotificationListCount(NotificationVO vo) throws Exception;
/**
* ( ) .
* .
* @param userId ID
* @return
* @throws Exception
*/
int selectUnreadNotificationCount(String userId) throws Exception;
/**
* (CONF_YN) 'Y' .
* @param ntfcSn (PK)
* @throws Exception
*/
void updateNotificationConfirm(long ntfcSn) throws Exception;
/**
* .
* .
* @param vo
* @throws Exception
*/
void insertNotification(NotificationVO vo) throws Exception;
/**
* URL .
* .
* @param vo
* @throws Exception
*/
void updateNotification(NotificationVO vo) throws Exception;
/**
* .
* @param ntfcSn
* @throws Exception
*/
void deleteNotification(long ntfcSn) throws Exception;
}

View File

@ -0,0 +1,62 @@
package geoinfo.ntfc.service;
import java.util.List;
/**
*
* , CRUD .
*/
public interface NotificationService {
/**
* . ( )
* @param vo (recvUserId, firstRow, lastRow )
* @return
* @throws Exception
*/
List<NotificationVO> selectNotificationList(NotificationVO vo) throws Exception;
/**
* . ( )
* @param vo
* @return
* @throws Exception
*/
int selectNotificationListCount(NotificationVO vo) throws Exception;
/**
* ( ) . ( )
* @param userId ID
* @return
* @throws Exception
*/
int selectUnreadNotificationCount(String userId) throws Exception;
/**
* () .
* @param ntfcSn
* @throws Exception
*/
void updateNotificationConfirm(long ntfcSn) throws Exception;
/**
* . ( )
* @param vo
* @throws Exception
*/
void insertNotification(NotificationVO vo) throws Exception;
/**
* . ()
* @param vo
* @throws Exception
*/
void updateNotification(NotificationVO vo) throws Exception;
/**
* . ()
* @param ntfcSn
* @throws Exception
*/
void deleteNotification(long ntfcSn) throws Exception;
}

View File

@ -0,0 +1,47 @@
package geoinfo.ntfc.service;
import java.io.Serializable;
/**
* VO
*/
public class NotificationVO implements Serializable {
private static final long serialVersionUID = 1L;
private long ntfcSn; // 알림순번
private String recvUserId; // 수신사용자ID
private String ntfcSeCode; // 알림구분코드
private String ntfcCn; // 알림내용
private String linkUrl; // 연결URL
private String linkMthdCode; // 연결방식코드 (GET/POST)
private String linkParamCn; // 연결파라미터내용 (JSON 형식 등)
private String confYn; // 확인여부
private String confDt; // 확인일시
private String rgtrId; // 등록자ID
private String regDt; // 등록일시
// Getter 및 Setter
public long getNtfcSn() { return ntfcSn; }
public void setNtfcSn(long ntfcSn) { this.ntfcSn = ntfcSn; }
public String getRecvUserId() { return recvUserId; }
public void setRecvUserId(String recvUserId) { this.recvUserId = recvUserId; }
public String getNtfcSeCode() { return ntfcSeCode; }
public void setNtfcSeCode(String ntfcSeCode) { this.ntfcSeCode = ntfcSeCode; }
public String getNtfcCn() { return ntfcCn; }
public void setNtfcCn(String ntfcCn) { this.ntfcCn = ntfcCn; }
public String getLinkUrl() { return linkUrl; }
public void setLinkUrl(String linkUrl) { this.linkUrl = linkUrl; }
public String getLinkMthdCode() { return linkMthdCode; }
public void setLinkMthdCode(String linkMthdCode) { this.linkMthdCode = linkMthdCode; }
public String getLinkParamCn() { return linkParamCn; }
public void setLinkParamCn(String linkParamCn) { this.linkParamCn = linkParamCn; }
public String getConfYn() { return confYn; }
public void setConfYn(String confYn) { this.confYn = confYn; }
public String getConfDt() { return confDt; }
public void setConfDt(String confDt) { this.confDt = confDt; }
public String getRgtrId() { return rgtrId; }
public void setRgtrId(String rgtrId) { this.rgtrId = rgtrId; }
public String getRegDt() { return regDt; }
public void setRegDt(String regDt) { this.regDt = regDt; }
}

View File

@ -0,0 +1,79 @@
package geoinfo.ntfc.service.impl;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import geoinfo.ntfc.service.NotificationMapper;
import geoinfo.ntfc.service.NotificationService;
import geoinfo.ntfc.service.NotificationVO;
/**
*
* Mapper DB .
*/
@Service("notificationService")
public class NotificationServiceImpl implements NotificationService {
/** 알림 데이터 접근을 위한 Mapper 주입 */
@Resource(name="notificationMapper")
private NotificationMapper notificationMapper;
/**
*
*/
@Override
public List<NotificationVO> selectNotificationList(NotificationVO vo) throws Exception {
return notificationMapper.selectNotificationList(vo);
}
/**
* ()
*/
@Override
public int selectNotificationListCount(NotificationVO vo) throws Exception {
return notificationMapper.selectNotificationListCount(vo);
}
/**
*
*/
@Override
public int selectUnreadNotificationCount(String userId) throws Exception {
return notificationMapper.selectUnreadNotificationCount(userId);
}
/**
* ()
* (CONF_YN) .
*/
@Override
public void updateNotificationConfirm(long ntfcSn) throws Exception {
notificationMapper.updateNotificationConfirm(ntfcSn);
}
/**
*
* .
*/
@Override
public void insertNotification(NotificationVO vo) throws Exception {
notificationMapper.insertNotification(vo);
}
/**
*
*/
@Override
public void updateNotification(NotificationVO vo) throws Exception {
notificationMapper.updateNotification(vo);
}
/**
*
*/
@Override
public void deleteNotification(long ntfcSn) throws Exception {
notificationMapper.deleteNotification(ntfcSn);
}
}

View File

@ -79,7 +79,7 @@ public class ProjectListController {
} else {
jsonObject.put("result", "false");
jsonObject.put("resultCode", "The name already exists.");
jsonObject.put("message", "해당 사업명이 이미 있습니다. 다른 사업명으로 입력해 주세요. code 1");
jsonObject.put("message", "해당 사업명이 이미 있습니다. 다른 사업명으로 입력해 주세요. code 1\n프로젝트 코드:[" + arrProjectCodeAndProjectName.get(0).get("PROJECT_CODE") + "]");
}
} else if( 0 < arrProjectCodeAndProjectNameByProjectNameFromTempMetaInfo.size() ) {

View File

@ -0,0 +1,64 @@
<?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="geoinfo.ntfc.service.NotificationMapper">
<select id="selectNotificationList" parameterType="geoinfo.ntfc.service.NotificationVO" resultType="geoinfo.ntfc.service.NotificationVO">
SELECT
NTFC_SN, RECV_USER_ID, NTFC_SE_CODE, NTFC_CN,
LINK_URL, LINK_MTHD_CODE, LINK_PARAM_CN,
CONF_YN, TO_CHAR(CONF_DT, 'YYYY-MM-DD HH24:MI:SS') as CONF_DT,
RGTR_ID, TO_CHAR(REG_DT, 'YYYY-MM-DD HH24:MI:SS') as REG_DT
FROM TBL_NTFC
WHERE RECV_USER_ID = #{recvUserId}
ORDER BY REG_DT DESC
</select>
<select id="selectNotificationListCount" parameterType="geoinfo.ntfc.service.NotificationVO" resultType="int">
SELECT COUNT(*)
FROM TBL_NTFC
WHERE RECV_USER_ID = #{recvUserId}
<if test="confYn != null and confYn != ''">
AND CONF_YN = #{confYn}
</if>
</select>
<select id="selectUnreadNotificationCount" parameterType="String" resultType="int">
SELECT COUNT(*)
FROM TBL_NTFC
WHERE RECV_USER_ID = #{userId}
AND CONF_YN = 'N'
</select>
<update id="updateNotificationConfirm" parameterType="long">
UPDATE TBL_NTFC
SET CONF_YN = 'Y',
CONF_DT = SYSDATE
WHERE NTFC_SN = #{ntfcSn}
</update>
<insert id="insertNotification" parameterType="geoinfo.ntfc.service.NotificationVO">
INSERT INTO TBL_NTFC (
NTFC_SN, RECV_USER_ID, NTFC_SE_CODE, NTFC_CN,
LINK_URL, LINK_MTHD_CODE, LINK_PARAM_CN, RGTR_ID
) VALUES (
TBL_NTFC_SN_SEQ.NEXTVAL, #{recvUserId}, #{ntfcSeCode}, #{ntfcCn},
#{linkUrl}, #{linkMthdCode}, #{linkParamCn}, #{rgtrId}
)
</insert>
<update id="updateNotification" parameterType="geoinfo.ntfc.service.NotificationVO">
UPDATE TBL_NTFC
SET NTFC_SE_CODE = #{ntfcSeCode},
NTFC_CN = #{ntfcCn},
LINK_URL = #{linkUrl},
LINK_MTHD_CODE = #{linkMthdCode},
LINK_PARAM_CN = #{linkParamCn}
WHERE NTFC_SN = #{ntfcSn}
</update>
<delete id="deleteNotification" parameterType="long">
DELETE FROM TBL_NTFC
WHERE NTFC_SN = #{ntfcSn}
</delete>
</mapper>

View File

@ -0,0 +1,192 @@
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<script>
$(document).ready(function(){
paging();
// 페이지 로드 시 현재 목록의 미확인 알림들을 확인 처리함
fn_markAsReadOnLoad();
$(document).on("click", ".ntfc-row", function() {
var sn = $(this).data("sn");
var url = $(this).data("url");
var method = $(this).data("method");
// .data()로 가져오면 JSON 문자열이 자동으로 객체로 변환될 수 있음
var param = $(this).attr("data-param");
fn_ntfcClick(sn, url, method, param);
});
});
var prePage = '<c:out value='${params.prePage}'/>';
var nowPage = '<c:out value='${params.nowPage}'/>';
var startPage = '<c:out value='${params.startPage}'/>';
var endPage = '<c:out value='${params.endPage}'/>';
var nextPage = '<c:out value='${params.nextPage}'/>';
function paging(){
var text = "";
text += '<a href="#" onclick="goPage('+prePage+')" class="btn btn-small btn-transparent-dark-gray btn-rounded md-margin-15px-bottom sm-display-table sm-margin-lr-auto"><i class="fa fa-angle-double-left icon-medium" aria-hidden="true"></i></a>';
text += '<a href="#" onclick="goPage('+startPage+')" class="btn btn-small btn-transparent-dark-gray btn-rounded md-margin-15px-bottom sm-display-table sm-margin-lr-auto"><i class="fa fa-angle-left icon-very-medium" aria-hidden="true"></i></a>';
for(var i = parseInt(startPage); i <= parseInt(endPage); i++){
var page = parseInt(i)+parseInt(1);
if(i == nowPage){
text += '<a href="javascript:goPage('+i+')"><b><span class="red">'+page+'</span></b></a>';
}else{
text += '<a href="javascript:goPage('+i+')">'+page+'</a>';
}
}
text += '<a href="#" onclick="javascript:goPage('+endPage+')" class="btn btn-small btn-transparent-dark-gray btn-rounded md-margin-15px-bottom sm-display-table sm-margin-lr-auto"><i class="fa fa-angle-right icon-very-medium" aria-hidden="true"></i></a>';
text += '<a href="#" onclick="javascript:goPage('+nextPage+')" class="btn btn-small btn-transparent-dark-gray btn-rounded md-margin-15px-bottom sm-display-table sm-margin-lr-auto"><i class="fa fa-angle-double-right icon-medium" aria-hidden="true"></i></a>';
$("#paging").html(text);
}
/**
* 현재 화면에 로딩된 미확인 알림들을 일괄 읽음 처리
*/
function fn_markAsReadOnLoad() {
var unreadSns = [];
// 'ntfc-row' 클래스 중 상태가 'N'(미확인)인 행의 SN 수집
$(".ntfc-row[data-conf='N']").each(function() {
unreadSns.push($(this).data("sn"));
});
if (unreadSns.length > 0) {
$.ajax({
url: "<c:url value='/ntfc/updateBulkRead.do'/>",
type: "POST",
data: { "ntfcSns": unreadSns.join(",") },
success: function(response) {
if(response.resultCode == 200) {
// UI 업데이트: 굵은 글씨 해제 및 라벨 변경
$(".ntfc-row[data-conf='N']").each(function() {
$(this).css("font-weight", "normal");
$(this).find(".label-danger").removeClass("label-danger").addClass("label-default").text("확인됨");
$(this).attr("data-conf", "Y"); // 상태값 동기화
});
// 상단 헤더의 알림 개수 갱신 (top.jsp의 함수 호출)
if (typeof fn_getUnreadNtfcCount === "function") {
fn_getUnreadNtfcCount();
}
}
},
error: function() {
console.error("일괄 읽음 처리 중 오류 발생");
}
});
}
}
/**
* 알림 클릭 처리 및 페이지 이동
*/
function fn_ntfcClick(ntfcSn, url, method, paramCn) {
var targetRow = $(".ntfc-row[data-sn='" + ntfcSn + "']");
var isUnread = targetRow.attr("data-conf") === 'N';
var moveAction = function() {
if(!url || url === "" || url === "#") {
location.reload();
return;
}
if(method === 'POST') {
var $form = $('<form>', { action: url, method: 'post' });
if(paramCn) {
try {
// 문자열이면 파싱하고, 이미 객체면 그대로 사용
var params = (typeof paramCn === 'string') ? JSON.parse(paramCn) : paramCn;
for(var key in params) {
$form.append($('<input>', {type: 'hidden', name: key, value: params[key]}));
}
} catch(e) { console.error("파라미터 파싱 오류", e); }
}
$('body').append($form);
$form.submit();
} else {
location.href = url;
}
};
if (isUnread) {
$.ajax({
url: "<c:url value='/ntfc/clickAction.do'/>",
type: "POST",
data: { "ntfcSn": ntfcSn },
success: function() { moveAction(); },
error: function() { moveAction(); }
});
} else {
moveAction();
}
}
function goPage(page) {
window.location.href="topMenuSelect.do?url=mypage_ntfc&page="+page;
}
</script>
<form name="listForm">
<div class="contents-row padT20">
<div class="table-scrollable">
<table id="Table_Main" class="table table-bordered" summary="알림 목록">
<caption>구분, 내용, 등록일, 확인여부</caption>
<colgroup>
<col style="width:10%;">
<col style="width:70%;">
<col style="width:20%;">
<col style="width:0%;">
<col style="width:0%;">
</colgroup>
<thead>
<tr>
<th>구분</th>
<th>알림 내용</th>
<th>등록일</th>
<th style="display: none;">상태</th>
<th style="display: none;">확인일</th>
</tr>
</thead>
<tbody>
<c:forEach var="item" items="${items}">
<tr height="35" align="center" class="ntfc-row"
data-sn="${item.ntfcSn}"
data-conf="${item.confYn}"
data-url="${item.linkUrl}"
data-method="${item.linkMthdCode}"
data-param='${item.linkParamCn}'
style="cursor: pointer; ${item.confYn == 'N' ? 'font-weight:bold;' : ''}">
<td>
<c:choose>
<c:when test="${item.ntfcSeCode == 'PRJ_NEW'}">프로젝트 배정</c:when>
<c:when test="${item.ntfcSeCode == 'PRJ_DEL'}">프로젝트 배정취소</c:when>
<c:otherwise>일반</c:otherwise>
</c:choose>
</td>
<td align="left" style="padding-left:15px;">${item.ntfcCn}</td>
<td>${item.regDt}</td>
<td style="display: none;">
<c:if test="${item.confYn == 'N'}"><span class="label label-danger">미확인</span></c:if>
<c:if test="${item.confYn == 'Y'}"><span class="label label-default">확인됨</span></c:if>
</td>
<td style="display: none;">${empty item.confDt ? '-' : item.confDt}</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
<div class="contents-row">
<table class="table-paging">
<tr>
<td align="center" id="paging"></td>
</tr>
</table>
</div>
</form>

View File

@ -177,8 +177,45 @@
//document.body.classList.add("ie");
document.documentElement.classList.add("ie");
}
// 로그인 상태일 때만 알림 개수를 체크합니다.
<c:if test="${isLogin == true}">
fn_getUnreadNtfcCount();
</c:if>
});
/**
* 읽지 않은 알림 개수를 조회하여 반영한다.
*/
function fn_getUnreadNtfcCount() {
$.ajax({
url: "<c:url value='/ntfc/getUnreadCount.do'/>",
type: "GET",
dataType: "json",
success: function(data) {
/*
if (data.count > 0) {
$("#ntfc_unread_cnt").text(data.count).show();
} else {
$("#ntfc_unread_cnt").hide();
}
*/
$("#ntfc_unread_cnt").text(data.count).show();
},
error: function(err) {
console.error("알림 개수 조회 중 오류 발생");
}
});
}
/**
* 알림 내역 버튼 클릭 시 실행 (기존 함수가 없다면 아래 참고)
*/
function goNtfc() {
// 알림 리스트 팝업이나 페이지 이동 로직
location.href = "topMenuSelect.do?url=mypage_ntfc";
}
function gomypage() {
top.location.href = "topMenuSelect.do?url=mypage_main";
}

View File

@ -223,6 +223,11 @@
<span class="title">비밀번호 수정</span>
</a>
</li>
<li class="nav-item">
<a href="#" onClick="go_passch('inntfc')" onFocus="this.blur()" class="nav-link nav-toggle">
<span class="title">알림내역</span>
</a>
</li>
<li class="nav-item">
<a href="#" onClick="go_passch('in7')" onFocus="this.blur()" class="nav-link nav-toggle">
<span class="title">장바구니</span>

View File

@ -24,6 +24,11 @@
<ul>
<!-- 로그인시 시작 -->
<c:if test="${isLogin == true}">
<li>
<a href="#" onclick="javascript:goNtfc()" class="top-btn btn-ntfc">
알림내역 <span id="ntfc_unread_cnt" class="badge badge-danger" style="display:none;">0</span>
</a>
</li>
<li>
<c:if test="${cls != 1}">
<span class="username-zone"><span class="username">${username}(${userid})</span>님 반갑습니다.</span>

View File

@ -127,24 +127,20 @@
function go_passch(load) {
if (load == 'in') {
top.location.href = "topMenuSelect.do?url=mypage";
}
if (load == 'in2') {
} else if (load == 'in2') {
top.location.href = "topMenuSelect.do?url=mypage_main";
}
if (load == 'in3') {
} else if (load == 'in3') {
top.location.href = "topMenuSelect.do?url=mypage_passch";
}
if (load == 'in4') {
} else if (load == 'in4') {
top.location.href = "topMenuSelect.do?url=loginCount_reset";
}
if (load == 'in5') {
} else if (load == 'in5') {
top.location.href = "topMenuSelect.do?url=joinEx";
}
if (load == 'in6') {
} else if (load == 'in6') {
top.location.href = "topMenuSelect.do?url=jusandoNew";
}
if (load == 'in7') {
} else if (load == 'in7') {
top.location.href = "topMenuSelect.do?url=mypage_cart";
} else if (load == 'inntfc') {
top.location.href = "topMenuSelect.do?url=mypage_ntfc"; //Notification 알림내역
}
}
@ -488,6 +484,23 @@
});
</script>
<!-- 마이페이지 > 비밀번호 수정 끝 -->
</c:if><c:if test="${eGovUrl == 'mypage_ntfc.do' }">
<!-- 마이페이지 > 알림내역 시작 -->
<h1 class="page-title">
<span class="page-title-text">알림내역</span>
<ul class="page-category">
<li class="category-item">마이페이지</li>
<li class="category-item">알림내역</li>
</ul>
</h1>
<script>
$(document).ready(function(){
// 왼쪽메뉴 활성화
$("#mypage_sub_menu").css("display", "block");
$("#mypage_sub_menu > li.nav-item:eq(2)").addClass("active");
});
</script>
<!-- 마이페이지 > 알림내역 수정 끝 -->
</c:if>
<c:if test="${eGovUrl == 'mypage_cart.do' }">
<!-- 마이페이지 > 장바구니 수정 시작 -->
@ -502,7 +515,7 @@
$(document).ready(function(){
// 왼쪽메뉴 활성화
$("#mypage_sub_menu").css("display", "block");
$("#mypage_sub_menu > li.nav-item:eq(2)").addClass("active");
$("#mypage_sub_menu > li.nav-item:eq(3)").addClass("active");
});
</script>
<!-- 마이페이지 > 비밀번호 수정 끝 -->