diff --git a/src/main/java/geoinfo/drilling/input/service/impl/DrillingInputServiceImpl.java b/src/main/java/geoinfo/drilling/input/service/impl/DrillingInputServiceImpl.java index 1ca1ac1e..5bfcb185 100644 --- a/src/main/java/geoinfo/drilling/input/service/impl/DrillingInputServiceImpl.java +++ b/src/main/java/geoinfo/drilling/input/service/impl/DrillingInputServiceImpl.java @@ -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; /** @@ -106,7 +110,7 @@ public class DrillingInputServiceImpl implements DrillingInputService { */ if (!"".equals(addConstUserid)) { params.put("constUserId", addConstUserid); - params.put("holeNumber", -999); + params.put("holeNumber", -999); saveAndInsertMeta(params, request, response); } return params; @@ -123,24 +127,63 @@ public class DrillingInputServiceImpl implements DrillingInputService { JSONObject tempConstructSiteInfo = drillingInquiryService.drillingInquiryOneItem(request, params); // JSONObject를 HashMap으로 변환 ArrayList arrayList = (ArrayList) MyUtil.JSONObjectToHashMap( tempConstructSiteInfo ).get("datas"); - HashMap oldTempConstructSiteInfo = new HashMap(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 oldTempConstructSiteInfo = new HashMap(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 params, HashMap 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 selectConstructCompanyList(HashMap params) throws Exception { List list = new ArrayList(); @@ -347,7 +390,7 @@ public class DrillingInputServiceImpl implements DrillingInputService { // JSONObject를 HashMap으로 변환 ArrayList arrayList = (ArrayList) MyUtil.JSONObjectToHashMap( tempConstructSiteInfo ).get("datas"); HashMap oldTempConstructSiteInfo = new HashMap(arrayList.get(0)); - + String projectCode = MyUtil.getStringFromObject( oldTempConstructSiteInfo.get("projectCode") ); HashMap 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,12 +453,12 @@ 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); + params.put("holeNumber", -999); } saveAndInsertMeta(params, request, response); drillingInputMapper.spUdtTblCsi(spUdtTblCsiParams); @@ -429,7 +474,9 @@ public class DrillingInputServiceImpl implements DrillingInputService { } else { // 5-2) deleteTempMetaInfo(oldTempConstructSiteInfo); deleteTempProjectInfo(oldTempConstructSiteInfo); - params.put("holeNumber", -999); + // 프로젝트 삭제를 기업사용자의 알림내역에 추가한다. + deleteNotification(request, response, params, oldTempConstructSiteInfo, projectCode); + params.put("holeNumber", -999); params.put("constProjectCode", oldTempConstructSiteInfo.get("projectCode")); // 기존에 부여된 PROJECT_CODE 유지 params.put("constUserId", addConstUserid); // 새로 선정한 건설사계정 saveAndInsertMeta(params, request, response); @@ -526,13 +573,67 @@ public class DrillingInputServiceImpl implements DrillingInputService { metaParams.put("rUrl", ""); // 2. ProjectListController의 insertMeta 호출 - ModelAndView model = new ModelAndView(); - + 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 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 resultMap = new HashMap<>(); + resultMap.put("resultCode", 200); + resultMap.put("message", "기업사용자에게 알림이 전송되었습니다."); + } catch (Exception e) { + // 알림 등록 실패 시 로그 처리 (비즈니스 로직에 큰 영향이 없다면 에러를 던지지 않고 로그만 남김) + e.printStackTrace(); + } + } else { + // 필수 정보 누락 시 처리 + System.out.println("알림 발송 실패: 수신자 ID 또는 등록자 ID가 존재하지 않습니다."); + } + } /** * 건설현장 수정 시, 특정 기업사용자에서 다른 기업사용자로 변경하면 기존 기업사용자의 프로젝트제거하는 메서드 */ @@ -547,8 +648,29 @@ public class DrillingInputServiceImpl implements DrillingInputService { // 2. ProjectListController의 deleteAll 호출 ModelAndView model = new ModelAndView(); - model = projectListService.deleteAll(deleteParams, model, request, response); // deleteAll 호출 시 발생되는 오류는 조치하기. + model = projectListService.deleteAll(deleteParams, model, request, response); // deleteAll 호출 시 발생되는 오류는 조치하기. + return model; } + + /** + * 건설현장 삭제 시, 해당 프로젝트를 기업사용자가 이미 수정을 시작했는지 판단하는 method. + * @return + * @throws SQLException + */ + private boolean isInputInProgress(HashMap 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; + } } diff --git a/src/main/java/geoinfo/main/login/LoginController.java b/src/main/java/geoinfo/main/login/LoginController.java index aad23d21..1974e0ba 100644 --- a/src/main/java/geoinfo/main/login/LoginController.java +++ b/src/main/java/geoinfo/main/login/LoginController.java @@ -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; @@ -87,6 +89,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(); @@ -1732,6 +1737,61 @@ public class LoginController { return mv; } + + // 마이페이지 알림내역. + @RequestMapping(value = "/mypage_ntfc.do") + public ModelAndView mypage_ntfc(ModelMap model, @RequestParam Map 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 items = notificationService.selectNotificationList(vo); // 페이징 쿼리 적용 필요 + + model.put("params", params); + model.put("items", items); + + return mv; + } // 마이페이지 장바구니. @RequestMapping(value = "/mypage_cart.do") diff --git a/src/main/java/geoinfo/main/main/MainController.java b/src/main/java/geoinfo/main/main/MainController.java index 7cec2454..59111707 100644 --- a/src/main/java/geoinfo/main/main/MainController.java +++ b/src/main/java/geoinfo/main/main/MainController.java @@ -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"; diff --git a/src/main/java/geoinfo/ntfc/NotificationController.java b/src/main/java/geoinfo/ntfc/NotificationController.java new file mode 100644 index 00000000..a53c54e6 --- /dev/null +++ b/src/main/java/geoinfo/ntfc/NotificationController.java @@ -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 getUnreadCount(HttpServletRequest request, HttpServletResponse response) throws Exception { + Map 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 getNtfcList(HttpServletRequest request, HttpServletResponse response) throws Exception { + Map 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 list = notificationService.selectNotificationList(vo); + resultMap.put("list", list); + return resultMap; + } + + /** + * 알림 클릭 시 확인 처리 및 연결 정보 반환 + */ + @RequestMapping(value = "/ntfc/clickAction.do") + @ResponseBody + public Map clickAction(HttpServletRequest request, HttpServletResponse response, @RequestParam("ntfcSn") long ntfcSn) throws Exception { + Map 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 updateBulkRead(HttpServletRequest request, @RequestParam("ntfcSns") String ntfcSns) throws Exception { + Map 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 insertNtfc(HttpServletRequest request, NotificationVO vo) throws Exception { + Map 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 updateNtfc(HttpServletRequest request, NotificationVO vo) throws Exception { + Map 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 deleteNtfc(HttpServletRequest request, @RequestParam("ntfcSn") long ntfcSn) throws Exception { + Map 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; + } +} \ No newline at end of file diff --git a/src/main/java/geoinfo/ntfc/service/NotificationMapper.java b/src/main/java/geoinfo/ntfc/service/NotificationMapper.java new file mode 100644 index 00000000..8ff9931d --- /dev/null +++ b/src/main/java/geoinfo/ntfc/service/NotificationMapper.java @@ -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 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; +} \ No newline at end of file diff --git a/src/main/java/geoinfo/ntfc/service/NotificationService.java b/src/main/java/geoinfo/ntfc/service/NotificationService.java new file mode 100644 index 00000000..b672d0bd --- /dev/null +++ b/src/main/java/geoinfo/ntfc/service/NotificationService.java @@ -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 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; +} \ No newline at end of file diff --git a/src/main/java/geoinfo/ntfc/service/NotificationVO.java b/src/main/java/geoinfo/ntfc/service/NotificationVO.java new file mode 100644 index 00000000..dd486e51 --- /dev/null +++ b/src/main/java/geoinfo/ntfc/service/NotificationVO.java @@ -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; } +} \ No newline at end of file diff --git a/src/main/java/geoinfo/ntfc/service/impl/NotificationServiceImpl.java b/src/main/java/geoinfo/ntfc/service/impl/NotificationServiceImpl.java new file mode 100644 index 00000000..d82c66db --- /dev/null +++ b/src/main/java/geoinfo/ntfc/service/impl/NotificationServiceImpl.java @@ -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 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); + } +} \ No newline at end of file diff --git a/src/main/java/geoinfo/regi/projectList/ProjectListController.java b/src/main/java/geoinfo/regi/projectList/ProjectListController.java index 4a617212..d7f18ffe 100644 --- a/src/main/java/geoinfo/regi/projectList/ProjectListController.java +++ b/src/main/java/geoinfo/regi/projectList/ProjectListController.java @@ -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() ) { diff --git a/src/main/resources/egovframework/sqlmap/mapper/ntfc/NotificationMapper.xml b/src/main/resources/egovframework/sqlmap/mapper/ntfc/NotificationMapper.xml new file mode 100644 index 00000000..4b3ed3f0 --- /dev/null +++ b/src/main/resources/egovframework/sqlmap/mapper/ntfc/NotificationMapper.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + UPDATE TBL_NTFC + SET CONF_YN = 'Y', + CONF_DT = SYSDATE + WHERE NTFC_SN = #{ntfcSn} + + + + 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} + ) + + + + 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} + + + + DELETE FROM TBL_NTFC + WHERE NTFC_SN = #{ntfcSn} + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/body/mypage/mypage_ntfc.jsp b/src/main/webapp/WEB-INF/views/body/mypage/mypage_ntfc.jsp new file mode 100644 index 00000000..a4efd2d5 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/body/mypage/mypage_ntfc.jsp @@ -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" %> + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
구분, 내용, 등록일, 확인여부
구분알림 내용등록일상태확인일
+ + 프로젝트 배정 + 프로젝트 배정취소 + 일반 + + ${item.ntfcCn}${item.regDt} + 미확인 + 확인됨 + ${empty item.confDt ? '-' : item.confDt}
+
+
+ +
+ + + + +
+
+
\ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/home/include/header.jsp b/src/main/webapp/WEB-INF/views/home/include/header.jsp index 845e4ea3..94a8a4ae 100644 --- a/src/main/webapp/WEB-INF/views/home/include/header.jsp +++ b/src/main/webapp/WEB-INF/views/home/include/header.jsp @@ -177,8 +177,45 @@ //document.body.classList.add("ie"); document.documentElement.classList.add("ie"); } + + // 로그인 상태일 때만 알림 개수를 체크합니다. + + fn_getUnreadNtfcCount(); + }); + /** + * 읽지 않은 알림 개수를 조회하여 반영한다. + */ + function fn_getUnreadNtfcCount() { + $.ajax({ + url: "", + 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"; } diff --git a/src/main/webapp/WEB-INF/views/home/include/left_menu.jsp b/src/main/webapp/WEB-INF/views/home/include/left_menu.jsp index 3ed4e3f5..6e478d71 100644 --- a/src/main/webapp/WEB-INF/views/home/include/left_menu.jsp +++ b/src/main/webapp/WEB-INF/views/home/include/left_menu.jsp @@ -223,6 +223,11 @@ 비밀번호 수정 +