diff --git a/WebContent/jsp/onl/apim/portalinquiry/portalInquiryMan.jsp b/WebContent/jsp/onl/apim/portalinquiry/portalInquiryMan.jsp
index 37de99d..d2c25ca 100644
--- a/WebContent/jsp/onl/apim/portalinquiry/portalInquiryMan.jsp
+++ b/WebContent/jsp/onl/apim/portalinquiry/portalInquiryMan.jsp
@@ -20,15 +20,22 @@
var select_orgId = "";
function formatInquiryStatus(cellvalue, options, rowObject) {
- return cellvalue === 'PENDING'
- ? '문의중'
- : '답변완료';
+ if (cellvalue == 'PENDING') {
+ return '문의중';
+ } else if (cellvalue == 'RESPONDED') {
+ return '답변완료'
+ } else if (cellvalue == 'CLOSED') {
+ return '답변종료'
+ } else {
+ return cellvalue;
+ }
}
function formatFile(cellvalue, options, rowObject) {
var iconPath = '${pageContext.request.contextPath}/images/icon_file.png';
var icon = rowObject.hasAttachment ? '
' : '';
- return cellvalue + icon;
+ var commentCount = rowObject.commentCount > 0 ? ' [' + rowObject.commentCount + ']' : '';
+ return cellvalue + icon + commentCount;
}
function deleteSelectedRows() {
diff --git a/WebContent/jsp/onl/apim/portalinquiry/portalInquiryManDetail.jsp b/WebContent/jsp/onl/apim/portalinquiry/portalInquiryManDetail.jsp
index a302d4e..a6cd313 100644
--- a/WebContent/jsp/onl/apim/portalinquiry/portalInquiryManDetail.jsp
+++ b/WebContent/jsp/onl/apim/portalinquiry/portalInquiryManDetail.jsp
@@ -84,6 +84,7 @@
var url = '';
var url_view = '';
var file_download = '';
+ var inquiryStatus = 'PENDING';
function decodeHTMLEntities(text) {
var textArea = document.createElement('textarea');
@@ -98,10 +99,21 @@
dataType: "json",
data: {cmd: 'DETAIL', id: key},
success: function (data) {
+
+
$("#id").val(key);
+
$("#inquirySubject").text(data.inquirySubject);
$("#inquiryDetail").html(data.inquiryDetail);
- $("#inquiryStatus").text(data.inquiryStatus === 'PENDING' ? '문의중' : '답변완료');
+
+ inquiryStatus = data.inquiryStatus;
+ if (data.inquiryStatus == 'PENDING') {
+ $("#inquiryStatus").text('문의중');
+ } else if (data.inquiryStatus == 'RESPONDED') {
+ $("#inquiryStatus").text('답변완료');
+ } else if (data.inquiryStatus == 'CLOSED') {
+ $("#inquiryStatus").text('답변종료');
+ }
$("#inquirer").text(data.inquirer.userName);
$("#createdDate").text(timeStampFormat(data.createdDate));
$("#responder").text(data.responderName || '');
@@ -158,6 +170,10 @@
}
function deleteComment(commentId, inquiryId) {
+ if (inquiryStatus == 'CLOSED') {
+ alert('답변이 종료되어 삭제할 수 없습니다');
+ return;
+ }
if (!confirm("댓글을 삭제하시겠습니까?")) return;
$.ajax({
type: "POST",
@@ -188,6 +204,10 @@
}
$("#btn_modify").click(function () {
+ if (inquiryStatus == 'CLOSED') {
+ alert('답변이 종료되어 수정할 수 없습니다');
+ return;
+ }
if (!checkRequired("ajaxForm")) return;
if (!confirm("<%= localeMessage.getString("common.checkSave")%>")) return;
@@ -209,7 +229,10 @@
});
$("#btn_comment").click(function () {
-
+ if (inquiryStatus == 'CLOSED') {
+ alert('답변이 종료되어 댓글을 등록할 수 없습니다');
+ return;
+ }
if (!confirm("<%= localeMessage.getString("common.checkSave")%>")) return;
$.ajax({
@@ -297,6 +320,10 @@
+
답변자
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/InquiryCommentRepository.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/InquiryCommentRepository.java
deleted file mode 100644
index caf0618..0000000
--- a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/InquiryCommentRepository.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.eactive.eai.rms.data.entity.onl.apim.portalinquiry;
-
-import com.eactive.apim.portal.qna.entity.InquiryComment;
-import com.eactive.eai.data.jpa.BaseRepository;
-import com.eactive.eai.rms.data.EMSDataSource;
-
-import java.util.List;
-
-@EMSDataSource
-public interface InquiryCommentRepository extends BaseRepository
{
-
- long countByInquiry_Id(String inquiryId);
-
- List findByInquiry_IdAndDelYnOrderBySeqAsc(String inquiryId, String delYn);
-}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentRepository.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentRepository.java
new file mode 100644
index 0000000..6fc58f4
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentRepository.java
@@ -0,0 +1,28 @@
+package com.eactive.eai.rms.data.entity.onl.apim.portalinquiry;
+
+import com.eactive.apim.portal.qna.entity.InquiryComment;
+import com.eactive.eai.data.jpa.BaseRepository;
+import com.eactive.eai.rms.data.EMSDataSource;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+@EMSDataSource
+public interface PortalInquiryCommentRepository extends BaseRepository {
+
+ long countByInquiry_Id(String inquiryId);
+
+ long countByInquiry_IdAndDelYn(String inquiryId, String delYn);
+
+ List findByInquiry_IdAndDelYnOrderByCreatedDateAsc(String inquiryId, String delYn);
+
+ Optional findTopByInquiry_IdAndDelYnOrderByCreatedDateDesc(
+ String inquiryId, String delYn);
+
+ Optional findTopByInquiry_IdAndAdminYnAndDelYnOrderByCreatedDateDesc(
+ String inquiryId, String adminYn, String delYn);
+
+ boolean existsByInquiry_IdAndAdminYnAndDelYnAndCreatedDateAfter(
+ String inquiryId, String adminYn, String delYn, LocalDateTime after);
+}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentService.java
index b13b422..0ccbb30 100644
--- a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentService.java
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentService.java
@@ -10,18 +10,22 @@ import java.util.List;
@Service
@Transactional(transactionManager = "transactionManagerForEMS")
-public class PortalInquiryCommentService extends AbstractEMSDataSerivce {
+public class PortalInquiryCommentService extends AbstractEMSDataSerivce {
// AbstractEMSDataSerivce의 repository 필드가 BaseRepository로 고정되어
// 커스텀 메서드를 호출할 수 없으므로 별도 필드로 직접 주입
@Autowired
- private InquiryCommentRepository inquiryCommentRepository;
+ private PortalInquiryCommentRepository inquiryCommentRepository;
public long countByInquiryId(String inquiryId) {
return inquiryCommentRepository.countByInquiry_Id(inquiryId);
}
+ public long countActiveByInquiryId(String inquiryId) {
+ return inquiryCommentRepository.countByInquiry_IdAndDelYn(inquiryId, "N");
+ }
+
public List findByInquiryId(String inquiryId) {
- return inquiryCommentRepository.findByInquiry_IdAndDelYnOrderBySeqAsc(inquiryId, "N");
+ return inquiryCommentRepository.findByInquiry_IdAndDelYnOrderByCreatedDateAsc(inquiryId, "N");
}
}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryRepository.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryRepository.java
index 3dc9f19..9dd631a 100644
--- a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryRepository.java
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryRepository.java
@@ -4,7 +4,10 @@ import com.eactive.apim.portal.qna.entity.Inquiry;
import com.eactive.eai.data.jpa.BaseRepository;
import com.eactive.eai.rms.data.EMSDataSource;
+import java.util.List;
+
@EMSDataSource
public interface PortalInquiryRepository extends BaseRepository {
+ List findByInquiryStatus(String inquiryStatus);
}
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryService.java b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryService.java
index bb20d6c..a9456d9 100644
--- a/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryService.java
+++ b/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryService.java
@@ -1,20 +1,28 @@
package com.eactive.eai.rms.data.entity.onl.apim.portalinquiry;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
import com.eactive.apim.portal.qna.entity.Inquiry;
import com.eactive.apim.portal.qna.entity.QInquiry;
import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce;
import com.querydsl.core.BooleanBuilder;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.Pageable;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import java.util.List;
@Service
@Transactional(transactionManager = "transactionManagerForEMS")
public class PortalInquiryService extends AbstractEMSDataSerivce {
+ // AbstractEMSDataSerivce의 repository 필드가 BaseRepository로 고정되어
+ // 커스텀 메서드를 호출할 수 없으므로 별도 필드로 직접 주입
+ @Autowired
+ private PortalInquiryRepository portalInquiryRepository;
+
public Page findAll(Pageable pageable, PortalInquirySearch portalInquirySearch) {
QInquiry qInquiry = QInquiry.inquiry;
@@ -58,4 +66,8 @@ public class PortalInquiryService extends AbstractEMSDataSerivce findByInquiryStatus(String inquiryStatus) {
+ return portalInquiryRepository.findByInquiryStatus(inquiryStatus);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/job/PortalInquiryClosingJob.java b/src/main/java/com/eactive/eai/rms/ext/djb/job/PortalInquiryClosingJob.java
new file mode 100644
index 0000000..2c976cd
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/ext/djb/job/PortalInquiryClosingJob.java
@@ -0,0 +1,73 @@
+package com.eactive.eai.rms.ext.djb.job;
+
+import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
+import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
+import com.eactive.eai.rms.common.util.CommonUtil;
+import com.eactive.eai.rms.onl.apim.portalInquiry.PortalInquiryClosingService;
+import com.eactive.eai.rms.onl.common.util.DateUtil;
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 매일 00:05 실행 - RESPONDED 상태 문의글 자동 종료 배치
+ *
+ * 종료 조건 (기본 7일):
+ *
+ * - 답변완료 후 댓글이 7일간 없는 경우
+ * - 관리자 댓글 등록 후 7일간 포탈사용자 댓글이 없는 경우
+ *
+ *
+ * Cron: 0 5 0 * * ?
+ *
+ * Job 파라미터 (JobDataMap):
+ *
+ * - {@value #KEY_INQUIRY_COMMENT_CLOSING_DAY}: 종료 기준 일수 (기본값: 7)
+ *
+ */
+@DisallowConcurrentExecution
+public class PortalInquiryClosingJob implements Job {
+
+ private static final Logger log = LoggerFactory.getLogger(PortalInquiryClosingJob.class);
+
+ /** Job 파라미터 키: 댓글종료 기간(일) */
+ public static final String KEY_INQUIRY_COMMENT_CLOSING_DAY = "inquiry.comment.closing_day";
+
+ private static final int DEFAULT_CLOSING_DAYS = 7;
+
+ @Autowired
+ private PortalInquiryClosingService inquiryCommentClosingService;
+
+ @Override
+ public void execute(JobExecutionContext context) throws JobExecutionException {
+ log.info("*** START PortalInquiryCommentClosingService run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
+
+ int closingDays = DEFAULT_CLOSING_DAYS;
+ String closingDayParam = context.getMergedJobDataMap().getString(KEY_INQUIRY_COMMENT_CLOSING_DAY);
+ if (closingDayParam != null) {
+ try {
+ closingDays = Integer.parseInt(closingDayParam);
+ } catch (NumberFormatException e) {
+ log.warn("잘못된 {} 파라미터 값: '{}', 기본값 {}일 사용", KEY_INQUIRY_COMMENT_CLOSING_DAY, closingDayParam, DEFAULT_CLOSING_DAYS);
+ }
+ }
+
+ DataSourceContextHolder.setDataSourceType(
+ DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
+ try {
+ int closedCount = inquiryCommentClosingService.closeExpiredInquiries(closingDays);
+ log.info("PortalInquiryCommentClosingService 완료: {}건 CLOSED 처리", closedCount);
+ } catch (Exception e) {
+ log.error("PortalInquiryCommentClosingService execution failed", e);
+ throw new JobExecutionException(e);
+ } finally {
+ DataSourceContextHolder.clearDataSourceType();
+ }
+
+ log.info("*** END PortalInquiryCommentClosingService run({})", DateUtil.getDateTime("yyyy-MM-dd HH:mm"));
+ }
+}
diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryClosingService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryClosingService.java
new file mode 100644
index 0000000..48b6dae
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryClosingService.java
@@ -0,0 +1,78 @@
+package com.eactive.eai.rms.onl.apim.portalInquiry;
+
+import com.eactive.apim.portal.qna.entity.Inquiry;
+import com.eactive.apim.portal.qna.entity.InquiryComment;
+import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryCommentRepository;
+import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryCommentService;
+import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * RESPONDED 상태 문의글 중 아래 조건에 해당하는 경우 CLOSED로 변경한다.
+ * - 조건 A: 활성 댓글이 없고 답변완료일(responseDate)로부터 N일 경과
+ * - 조건 B: 마지막 관리자 댓글(adminYn=Y) 이후 N일간 포탈사용자(adminYn=N) 댓글 없음
+ */
+@Service
+@Transactional(transactionManager = "transactionManagerForEMS")
+@Slf4j
+@RequiredArgsConstructor
+public class PortalInquiryClosingService {
+
+ private static final String RESPONDED = "RESPONDED";
+ private static final String CLOSED = "CLOSED";
+
+ private final PortalInquiryService portalInquiryService;
+ private final PortalInquiryCommentService portalInquiryCommentService;
+ private final PortalInquiryCommentRepository inquiryCommentRepository;
+
+ public int closeExpiredInquiries(int closingDays) {
+ LocalDateTime threshold = LocalDateTime.now().minusDays(closingDays);
+ List respondedInquiries = portalInquiryService.findByInquiryStatus(RESPONDED);
+
+ int closedCount = 0;
+ for (Inquiry inquiry : respondedInquiries) {
+ if (shouldClose(inquiry, threshold)) {
+ inquiry.setInquiryStatus(CLOSED);
+ portalInquiryService.save(inquiry);
+ closedCount++;
+ log.info("문의글 CLOSED 처리: id={}", inquiry.getId());
+ }
+ }
+ log.info("InquiryCommentClosingService: 총 {}건 CLOSED 처리 (기준={}일)", closedCount, closingDays);
+ return closedCount;
+ }
+
+ private boolean shouldClose(Inquiry inquiry, LocalDateTime threshold) {
+ long activeCommentCount = portalInquiryCommentService.countActiveByInquiryId(inquiry.getId());
+
+ // 댓글 없음 → 답변완료일 기준 N일 경과 여부만 확인
+ if (activeCommentCount == 0) {
+ LocalDateTime responseDate = inquiry.getResponseDate();
+ return responseDate != null && !responseDate.isAfter(threshold);
+ }
+
+ // 가장 최근 활성 댓글 확인
+ Optional lastComment = inquiryCommentRepository
+ .findTopByInquiry_IdAndDelYnOrderByCreatedDateDesc(inquiry.getId(), "N");
+
+ if (!lastComment.isPresent()) {
+ return false;
+ }
+
+ // 포탈사용자(adminYn != 'Y')가 마지막 댓글 작성자 → CLOSED 불가
+ if (!"Y".equals(lastComment.get().getAdminYn())) {
+ return false;
+ }
+
+ // 관리자가 마지막 댓글 작성자 → N일 경과 여부 확인
+ LocalDateTime lastAdminDate = lastComment.get().getCreatedDate();
+ return lastAdminDate != null && !lastAdminDate.isAfter(threshold);
+ }
+}
diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUI.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUI.java
index b5eddea..d609ca4 100644
--- a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUI.java
+++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUI.java
@@ -20,9 +20,7 @@ public class PortalInquiryCommentUI {
private String inquiryId;
private String commentDetail;
-
- private int seq;
-
+
private String delYn;
private String createdBy;
diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManService.java
index e9895ff..fff483c 100644
--- a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManService.java
+++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryManService.java
@@ -9,6 +9,7 @@ import com.eactive.apim.portal.qna.entity.Inquiry;
import com.eactive.apim.portal.qna.entity.InquiryComment;
import com.eactive.apim.portal.user.entity.UserInfo;
import com.eactive.eai.rms.common.base.BaseService;
+import com.eactive.eai.rms.common.exception.IntegrationException;
import com.eactive.eai.rms.data.entity.man.user.UserInfoService;
import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquiryCommentService;
import com.eactive.eai.rms.data.entity.onl.apim.portalinquiry.PortalInquirySearch;
@@ -41,6 +42,7 @@ public class PortalInquiryManService extends BaseService {
public static final String PENDING = "PENDING";
public static final String RESPONDED = "RESPONDED";
+ public static final String CLOSED = "CLOSED";
public PortalInquiryManService(PortalInquiryService portalInquiryService,
@@ -68,6 +70,7 @@ public class PortalInquiryManService extends BaseService {
if (ui.getInquirer() != null) {
maskPortalInquiryUI(ui);
}
+ ui.setCommentCount((int) portalInquiryCommentService.countActiveByInquiryId(inquiry.getId()));
return setInquiryAttachmentStatus(ui);
});
}
@@ -154,6 +157,7 @@ public class PortalInquiryManService extends BaseService {
UserInfo userinfo = userInfoService.getById(userId);
Inquiry inquiry = portalInquiryService.getById(portalInquiryUI.getId());
+ validateNotClosed(inquiry);
// 답변 대기중 상태일 경우만 답변완료로 상태 변경
if(inquiry.getInquiryStatus().equals(PENDING)) {
inquiry.setInquiryStatus(RESPONDED);
@@ -199,21 +203,28 @@ public class PortalInquiryManService extends BaseService {
public void insertComment(PortalInquiryCommentUI commentUI, String userId) {
Inquiry inquiry = portalInquiryService.getById(commentUI.getInquiryId());
+ validateNotClosed(inquiry);
InquiryComment comment = new InquiryComment();
comment.setInquiry(inquiry);
comment.setCommentDetail(commentUI.getCommentDetail());
comment.setDelYn("N");
- int nextSeq = (int) portalInquiryCommentService.countByInquiryId(commentUI.getInquiryId()) + 1;
- comment.setSeq(nextSeq);
+ comment.setAdminYn("Y");
portalInquiryCommentService.save(comment);
}
public void deleteComment(String commentId) {
InquiryComment comment = portalInquiryCommentService.getById(commentId);
+ validateNotClosed(comment.getInquiry());
comment.setDelYn("Y");
portalInquiryCommentService.save(comment);
}
+ private void validateNotClosed(Inquiry inquiry) {
+ if (CLOSED.equals(inquiry.getInquiryStatus())) {
+ throw new IntegrationException("답변종료되어 수정할 수 없습니다.");
+ }
+ }
+
}
diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryUI.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryUI.java
index fd48292..df3669c 100644
--- a/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryUI.java
+++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryUI.java
@@ -55,4 +55,6 @@ public class PortalInquiryUI {
private LocalDateTime lastModifiedDate;
private String orgName;
+
+ private int commentCount;
}