itsm_ysm/docs/SR_CHANGES_20260719.md

417 lines
18 KiB
Markdown

# ITMS SR 소스 변경 상세 (변경 전 → 변경 후) — 2026-07-19
> 리뷰·감사 추적용. 자격증명·AES 암호문·프로파일 yml·키파일은 변경 대상 아님(미기재). 빌드: `:api:compileJava :front:compileJava` BUILD SUCCESSFUL.
---
## SR#1 잠긴 계정 비밀번호 초기화 차단 + 메일 미발송 결함
### `api/src/main/java/com/urpsys/itmsapi/sys/controller/UserManageController.java` (관리자 초기화)
변경 전:
```java
LinkedHashMap<String, Object> userInfoMap = userManageService.selectUser(requestMap);
if(CommonUtil.nvl(userInfoMap.get("emailAdres")).equals(""))
{
resBody.put("resultCd" , "-1" );
resBody.put("resultMsg", "E-mail 이 존재하지 않습니다." );
return ResultData.ok(resBody);
}
```
변경 후:
```java
LinkedHashMap<String, Object> userInfoMap = userManageService.selectUser(requestMap);
// SR#1 : 계정이 잠겨 있으면 초기화 차단 (잠금 해제 후 가능)
if(CommonUtil.nvl(userInfoMap.get("lockAt")).equals("Y"))
{
resBody.put("resultCd" , "-2" );
resBody.put("resultMsg", "계정이 잠겨 있습니다. [로그인인증제한해제]로 잠금을 먼저 해제한 후 비밀번호를 초기화하세요." );
return ResultData.ok(resBody);
}
if(CommonUtil.nvl(userInfoMap.get("emailAdres")).equals(""))
{ ... 동일 ... }
```
### `api/src/main/java/com/urpsys/baseapi/main/controller/MainController.java` (로그인화면 초기화)
변경 전:
```java
else if(!CommonUtil.nvl(userInfoMap.get("emailAdres")).equals(requestMap.get("userEmail")))
{
resBody.put("resultCd" , -6 );
resBody.put("resultMsg", "사용자정보가 일치하지 않습니다." );
return ResultData.ok(resBody);
}
String strResetPassword = CommonUtil.getRandomChar(10);
```
변경 후:
```java
else if(!CommonUtil.nvl(userInfoMap.get("emailAdres")).equals(requestMap.get("userEmail")))
{ ... 동일 ... }
// SR#1 : 계정 잠금 시 초기화 차단 + 안내
else if(CommonUtil.nvl(userInfoMap.get("lockAt")).equals("Y"))
{
resBody.put("resultCd" , -7 );
resBody.put("resultMsg", "로그인 시도 횟수 초과로 계정이 잠겼습니다. " + getAdminDisplay() + "에게 문의 바랍니다." );
return ResultData.ok(resBody);
}
String strResetPassword = CommonUtil.getRandomChar(10);
```
### `front/.../webapp/WEB-INF/jsp/main/PopPasswordReset.jsp` (서버 안내 메시지 노출)
변경 전:
```javascript
else
{
alert("비밀번호 초기화에 실패하였습니다. \n사용자정보을 확인해주시길 바랍니다.");
}
```
변경 후:
```javascript
else
{
if(data.resultMsg != null && data.resultMsg != "") { alert(data.resultMsg); }
else { alert("비밀번호 초기화에 실패하였습니다. \n사용자정보을 확인해주시길 바랍니다."); }
}
```
---
## SR#2, SR#3, D03 로그인 메시지(잠금/오입력 구분 + 관리자 성명 동적)
### `front/src/main/java/com/urpsys/basefront/controller/MainController.java`
변경 전:
```java
if(srError[0].equals("-1")) { strErrorMsg = "고객님의 아이디는 5회 로그인 오류로 인해 잠금처리 되었습니다. 시스템 관리자에게 연락해 주세요."; }
else if(srError[0].equals("-2")) { strErrorMsg = "아이디 혹은 비밀번호을 확인해주세요. 5회 로그인 오류인경우 아이디가 잠금처리가 됩니다."; }
else if(srError[0].equals("-3")) { strErrorMsg = "아이디 혹은 비밀번호을 확인해주세요. 5회 로그인 오류인경우 아이디가 잠금처리가 됩니다."; }
```
변경 후:
```java
if(srError[0].equals("-1")) { strErrorMsg = "로그인 시도 횟수 초과로 계정이 잠겼습니다. " + getAdminDisplay() + "에게 문의 바랍니다."; }
else if(srError[0].equals("-2")) { strErrorMsg = "아이디 또는 패스워드가 잘못 되었습니다. 아이디와 패스워드를 정확히 입력해 주세요."; }
else if(srError[0].equals("-3")) { strErrorMsg = "아이디 또는 패스워드가 잘못 되었습니다. 아이디와 패스워드를 정확히 입력해 주세요."; }
```
추가(동적 관리자 표기 — 프로파일 yml `custom.adminName`/`custom.adminTitle` 미설정 시 "시스템 관리자"):
```java
@Value("${custom.adminName:}") private String strAdminName;
@Value("${custom.adminTitle:}") private String strAdminTitle;
private String getAdminDisplay() {
String n = (strAdminName==null)?"":strAdminName.trim();
String t = (strAdminTitle==null)?"":strAdminTitle.trim();
if(n.equals("")) return "시스템 관리자";
return "시스템 관리자(" + n + (t.equals("")?"":" "+t) + ")";
}
```
(api `baseapi/main/MainController`에도 동일 `getAdminDisplay()`/`@Value` 추가 — SR#1 잠금 메시지용)
---
## SR#7, SR#10 "등록" 버튼 → "임시저장"
### `front/.../jsp/itms/srm/{SvcDeskInsert,SvcDeskCopyInsert,ReportCheckInsert,ReportCheckCopyInsert,ReportIssueInsert,ReportIssueCopyInsert,ReportErrorInsert,ReportErrorCopyInsert,ReportOtherInsert,ReportOtherCopyInsert}.jsp` (10파일)
변경 전(각 파일 등록/승인요청 옆의 단독 등록 버튼):
```jsp
<button type="button" class="btn-etc" title="<spring:message code="button.create" />" onclick="fncXxxInsert(); return false;">
<spring:message code="button.create" />
</button>
```
변경 후:
```jsp
<button type="button" class="btn-etc" title="임시저장" onclick="fncXxxInsert(); return false;">
임시저장
</button>
```
(`등록/승인요청` 버튼은 그대로 유지. 단독 신청 버튼만 있는 `SvcDeskReqInsert.jsp`는 제외.)
---
## SR#8 중복 "새로만들기" 제거
### `front/.../jsp/itms/srm/ReportIssueDetail.jsp`
변경 전(승인요청 블록 + 수정/삭제 블록에 각각 새로만들기 → 2개 표출):
```jsp
<c:if test="${(privType eq 'mine' && report.inReportStatus ne 3) || privType eq 'super'}">
<!-- 새로만들기 기능 추가 -->
<button type="button" class="btn-etc" title="새로만들기" onclick="fncReportIssueCopyInsert(); return false;">새로만들기</button>
<button type="button" class="btn-etc" title="수정" onclick="fncReportIssueUpdate(); return false;">
```
변경 후(수정/삭제 블록의 중복 새로만들기 제거):
```jsp
<c:if test="${(privType eq 'mine' && report.inReportStatus ne 3) || privType eq 'super'}">
<!-- SR#8 중복 '새로만들기' 제거 (상단 승인요청 블록의 새로만들기로 일원화) -->
<button type="button" class="btn-etc" title="수정" onclick="fncReportIssueUpdate(); return false;">
```
---
## SR#9 조치예정자 직접선택 차단(신청) + 접수자/접수일시 서버제어(접수)
### `front/.../jsp/itms/srm/SvcDeskReqInsert.jsp` (조치예정자 돋보기 제거)
변경 전:
```jsp
<input type="text" name="vcChargeUser" id="vcChargeUser" style="width: 90%;" readonly value=""/>
<a href="<c:url value='/cmm/selectUserListPopup.do' />" ... onclick="fn_User_Search('조치예정', 'inChargeUser', '', 'vcChargeUser', '', '');return false;">
<img src="...btn_search.gif" alt="조치예정자 검색" title="조치예정자 검색"></a>
<input type="hidden" name="inChargeUser" id="inChargeUser" value="" >
```
변경 후:
```jsp
<!-- SR#9-1 : 서비스 선택에 따라 자동배정, 돋보기 제거 -->
<input type="text" name="vcChargeUser" id="vcChargeUser" style="width: 60%;" readonly value=""/>
<span style="color:#888;">(자동배정)</span>
<input type="hidden" name="inChargeUser" id="inChargeUser" value="" >
```
### `api/.../itmsapi/srm/controller/SvcDeskController.java` (접수자/접수일시 서버 강제)
변경 전: (클라이언트가 보낸 incidentVo 값을 그대로 서비스로 전달)
```java
LinkedHashMap<String, Object> returnMap = svcDeskService.addIncident(incidentVoMap, approvalsList, fileList, reqApprovalMap);
```
변경 후:
```java
// SR#9-2,9-3 : 접수자/접수일시 서버 권한값 강제
TokenInfo tokenInfoReg = CommonUtil.getAccessTokenInfo(request);
incidentVoMap.put("inRegUser" , tokenInfoReg.getUniqId());
incidentVoMap.put("vcRegUser" , tokenInfoReg.getUserName());
incidentVoMap.put("dtReceiptDatetime", CommonUtil.getKST("yyyy-MM-dd HH:mm"));
LinkedHashMap<String, Object> returnMap = svcDeskService.addIncident(incidentVoMap, approvalsList, fileList, reqApprovalMap);
```
(완료요청일시는 사용자 입력 성격상 유지)
---
## SR#12 입력값 길이 검증 + 이중 결함(장애페이지 후 등록) 차단
### `api/.../itmsapi/srm/controller/SvcDeskController.java` 및 `SvcDeskReqController.java` (등록 전 길이검증)
변경 전: 검증 없이 바로 등록 호출.
변경 후(양 컨트롤러 공통):
```java
String strTitle = CommonUtil.nvl(incidentVoMap.get("vcIncidentTitle"));
String strBody = CommonUtil.nvl(incidentVoMap.get("txIncidentBody"));
if(strTitle.length() > 200) { resBody.put("resultCd","-2"); resBody.put("resultMsg","제목은 200자 이내로 입력해 주세요. (현재 "+strTitle.length()+"자)"); return ResultData.ok(resBody); }
if(strBody.length() > 60000){ resBody.put("resultCd","-2"); resBody.put("resultMsg","본문 내용이 너무 깁니다. 60,000자 이내로 입력해 주세요."); return ResultData.ok(resBody); }
```
### `front/.../itmsfront/srm/controller/SvcDeskController.java` (커밋 후 메일 예외 격리)
변경 전:
```java
if(returnMap.get("resultCd").equals("0")) { commonService.sendIncidentMail(1, (int)returnMap.get("inIncidentSeq")); }
if(strReqApprovalYn.equals("Y") && returnMap.get("resultCd").equals("0")) { commonService.sendIncidentMail(3, (int)returnMap.get("inIncidentSeq")); }
```
변경 후:
```java
try {
if(returnMap.get("resultCd").equals("0")) { commonService.sendIncidentMail(1, (int)returnMap.get("inIncidentSeq")); }
if(strReqApprovalYn.equals("Y") && returnMap.get("resultCd").equals("0")) { commonService.sendIncidentMail(3, (int)returnMap.get("inIncidentSeq")); }
} catch(Exception mailEx) {
log.error("서비스 등록 후 메일발송 실패(등록은 정상): {}", mailEx.getMessage()); // 등록 결과에 영향 없음
}
```
### `front/.../jsp/itms/srm/{SvcDeskInsert,SvcDeskReqInsert}.jsp` (검증 메시지 노출 + maxlength)
변경 전:
```javascript
success: function(data) { alert("등록 완료 되었습니다."); fncSvcDeskList(); }
```
변경 후:
```javascript
success: function(data) {
if(data.resultCd != "0") { alert(data.resultMsg); return; } // SR#12 검증 실패 안내
alert("등록 완료 되었습니다."); fncSvcDeskList();
}
```
그리고 제목 input에 `maxlength="200"` 추가.
---
## SR#13 에디터 높이 무한 증가 → 최대높이
### `front/src/main/resources/static/js/tinymce_uritms_setting.js` (drawEditorWrite/drawEditorRead 2곳)
변경 전:
```javascript
height: 300,
min_height: 300, // 최소 높이
toolbar_mode: 'floating',
```
변경 후:
```javascript
height: 300,
min_height: 300, // 최소 높이
max_height: 600, // SR#13 최대 높이 제한(초과 시 내부 스크롤)
toolbar_mode: 'floating',
```
---
## SR#14 신청번호 부분검색 + SR#16 본인 신청건만 조회
### `api/.../mapper/.../itms/srm/SvcDeskReqMapper.xml`
변경 전:
```xml
<sql id="searchFilter">
<if test="incidentReqNum != null and incidentReqNum != ''"> AND A.VC_REQ_INCIDENT_NUM = #{incidentReqNum} </if>
...
</sql>
```
변경 후:
```xml
<sql id="searchFilter">
<!-- SR#14 부분검색 + 바인딩 -->
<if test="incidentReqNum != null and incidentReqNum != ''"> AND A.VC_REQ_INCIDENT_NUM LIKE CONCAT('%', #{incidentReqNum}, '%') </if>
...
<!-- SR#16 일반 사용자 본인 신청건만 -->
<if test="reqUserFilter != null and reqUserFilter != ''"> AND A.IN_REQ_USER = #{reqUserFilter} </if>
</sql>
```
### `api/.../itmsapi/srm/controller/SvcDeskReqController.java` (비관리자 필터 주입)
변경 후 추가:
```java
String strRoleReq = CommonUtil.nvl(tokenInfo.getAuthorities());
if(strRoleReq.indexOf("ROLE_ADMIN") < 0) { requestMap.put("reqUserFilter", tokenInfo.getUniqId()); }
```
---
## SR#19 오타 "없무권한" → "업무권한"
### `front/.../jsp/itms/error/errorNoAuth.jsp`
변경 전: `시스템 관리자에게 없무권한 부여후 사용하시길 바랍니다.`
변경 후: `시스템 관리자에게 업무권한 부여후 사용하시길 바랍니다.`
---
## SR#20 "인스턴트"(인스던트/인시던트) → "서비스"
front 모듈 전체 스윕 — JSP/JS/Java/XML 48파일 172건. 예:
- 변경 전: `<title> 인스던트 신청 </title>` / `confirm("인스던트 신청 처리를 하시겠습니까?")` / `<th> 인스던트 접수번호 </th>`
- 변경 후: `<title> 서비스 신청 </title>` / `confirm("서비스 신청 처리를 하시겠습니까?")` / `<th> 서비스 접수번호 </th>`
(명령: `sed 's/인스던트/서비스/g'`, `sed 's/인시던트/서비스/g'` — "인스턴스"(instance)는 미변경)
---
## SR#23 FOOTER 저작권 오타
### `front/.../jsp/main/inc/IncFooter.jsp`
변경 전: `COPYRIGHT © 20242 URP. All Right Reserved`
변경 후: `COPYRIGHT © 2024 URP. All Right Reserved`
---
## F01 보고서 양식명 전체 표시(툴팁)
### `front/.../jsp/itms/rem/ReportList.jsp`
변경 전: `<td><a href="javascript:fncSelectReport('${report.inReportForm}')" style="text-align: center;" >${report.vcFormName}</a></td>`
변경 후: `<td title="${report.vcFormName}"><a href="javascript:fncSelectReport('${report.inReportForm}')" title="${report.vcFormName}">${report.vcFormName}</a></td>`
---
## F03 서비스신청 서비스 항목 고정(SR기본요청양식)
### `front/.../jsp/itms/srm/SvcDeskReqInsert.jsp`
변경 후 추가(select 아래):
```javascript
$(function(){
var $s = $('#inServiceCode');
$s.find('option').each(function(){ if($.trim($(this).text())=='SR기본요청양식'){ $s.val($(this).val()); return false; } });
$s.css({'pointer-events':'none','background-color':'#f2f2f2'}).attr('tabindex','-1'); // 값은 제출, 변경 불가
});
```
---
## F05 그룹아이디 기본값 KIC
### `front/.../jsp/itms/sys/UserSelectUpdt.jsp`
변경 후 추가(select 아래):
```javascript
$(function(){
var $g = $('#groupId');
if($g.length>0 && ($g.val()==null || $g.val()=='')) {
$g.find('option').each(function(){ if($.trim($(this).text())=='KIC'){ $g.val($(this).val()); return false; } });
}
});
```
---
## F06 게시판 제목 클릭 진입
### `front/.../jsp/itms/srm/ReportCheckList.jsp`
변경 전(제목 = 일반 텍스트):
```jsp
<td title="${reportList.vcReportTitle}">
<c:choose><c:when test="${fn:length(reportList.vcReportTitle) > 25}">...</c:when><c:otherwise>...</c:otherwise></c:choose>
</td>
```
변경 후(제목 = 링크):
```jsp
<td title="${reportList.vcReportTitle}">
<a href="javascript:fncSelectReportCheck('${reportList.inReportSeq}')">
<c:choose>...</c:choose>
</a>
</td>
```
---
## F09 관리자 게시물/댓글 삭제 권한
### `front/.../itmsfront/bbs/controller/BBSAdminManageController.java`
변경 후 추가: `model.addAttribute("sessionAdminYn", (role.indexOf("ROLE_ADMIN")>-1)?"Y":"N");`
### `front/.../jsp/itms/bbs/admin/NoticeInqire.jsp`
변경 후: 작성자 삭제 블록 뒤에 관리자 전용 삭제 버튼 추가
```jsp
<c:if test="${result.frstRegisterId != sessionUniqId && sessionAdminYn == 'Y'}">
<button ... onclick="fn_egov_delete_notice(); return false;">삭제</button>
</c:if>
```
댓글 삭제 조건 변경 전: `<c:if test="${listCn.deleteYn eq 'Y' }">` → 변경 후: `<c:if test="${listCn.deleteYn eq 'Y' || sessionAdminYn == 'Y' }">`
---
## F10 게시판 조회수/댓글수 표기
### `api/.../mapper/.../itms/bbs/BBSAdminManageMapper.xml`
변경 후 추가(select 컬럼):
```xml
, ( SELECT COUNT(1) FROM TB_CMNT_INFO cc WHERE cc.NTT_ID = a.NTT_ID AND cc.USE_AT = 'Y' ) AS cmntCnt
```
### `front/.../jsp/itms/bbs/admin/NoticeList.jsp`
헤더에 `<th scope="col">댓글수</th>`, 본문에 `<td><c:out value="${result.cmntCnt}" /></td>` 추가(빈목록 colspan 7/5/4 → 8/6/5).
---
## F12 로고/파비콘 브랜딩 관리(KIC)
### 신규 `front/.../basefront/controller/BrandingController.java`
- `GET /branding/logo`·`/branding/favicon` : 업로드본 우선, 없으면 번들 KIC 기본자산(webapp `/branding/default/*`) 폴백. 캐시무효화 헤더.
- `GET /branding/brandingManageView.do` : 관리자 화면(비관리자 → 메인 리다이렉트).
- `POST /branding/upload.do` : 관리자 검증 + 확장자 화이트리스트(logo: svg/png/jpg/gif, favicon: ico/png/svg/jpg/gif) + 2MB 제한 + 저장파일명 서버고정(`kind.ext`, 사용자 파일명 미사용→경로조작 차단) + canonical 경로 재확인.
### `front/.../basefront/config/SecurityConfig.java`
변경 후 추가: `.antMatchers("/branding/logo", "/branding/favicon").permitAll()` (로그인 전 표출).
### 참조 변경(정적 로고/파비콘 → 동적 엔드포인트)
- `front/.../jsp/main/LoginUsr.jsp`·`LoginUsr_kic.jsp` : `${ctx}/images/main/logo.png``${ctx}/branding/logo`
- `front/.../jsp/tilesLayout/{uritms-base-main1,uritms-base-main2,uritms-base-popup1,uritms-popup1}.jsp` : `<link rel="shortcut icon" href="data:image/x-icon;,">``href="${pageContext.request.contextPath}/branding/favicon"`
- `front/.../jsp/main/inc/IncHeader.jsp` : `<h1 class="logo">``background-image:url('<c:url value="/branding/logo"/>')` 인라인 지정
### 신규 자산·화면
- `front/src/main/webapp/branding/default/logo.svg`·`favicon.jpg` (KIC 공식 자산 번들)
- `front/.../jsp/branding/brandingManage.jsp` (업로드/미리보기 화면)
---
## D04 검색 후 페이지네이션 검색조건 소실
### `front/.../jsp/itms/sys/AuthorGroupManage.jsp`
변경 전: `<ui:pagination paginationInfo="${paginationInfo}" type="image" jsFunction="fn_egov_select_linkPage"/>` (해당 함수 미정의 → 검색조건 유실)
변경 후: `<ui:pagination paginationInfo="${paginationInfo}" type="image" jsFunction="linkPage"/>` (listForm 재제출 → searchCondition/searchKeyword 유지)