Q&A 7일후 자동 종료 배치
This commit is contained in:
@@ -20,15 +20,22 @@
|
||||
var select_orgId = "";
|
||||
|
||||
function formatInquiryStatus(cellvalue, options, rowObject) {
|
||||
return cellvalue === 'PENDING'
|
||||
? '<span style="color: red;">문의중</span>'
|
||||
: '<span>답변완료</span>';
|
||||
if (cellvalue == 'PENDING') {
|
||||
return '<span style="color: red;">문의중</span>';
|
||||
} else if (cellvalue == 'RESPONDED') {
|
||||
return '<span >답변완료</span>'
|
||||
} else if (cellvalue == 'CLOSED') {
|
||||
return '<span >답변종료</span>'
|
||||
} else {
|
||||
return cellvalue;
|
||||
}
|
||||
}
|
||||
|
||||
function formatFile(cellvalue, options, rowObject) {
|
||||
var iconPath = '${pageContext.request.contextPath}/images/icon_file.png';
|
||||
var icon = rowObject.hasAttachment ? '<img src="' + iconPath + '" alt="File Attached" style="vertical-align: middle; margin-left: 5px; width: 16px; height: 16px;">' : '';
|
||||
return cellvalue + icon;
|
||||
var commentCount = rowObject.commentCount > 0 ? ' <span style="color: #337ab7;">[' + rowObject.commentCount + ']</span>' : '';
|
||||
return cellvalue + icon + commentCount;
|
||||
}
|
||||
|
||||
function deleteSelectedRows() {
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
var url = '<c:url value="/onl/apim/portalinquiry/portalInquiryMan.json" />';
|
||||
var url_view = '<c:url value="/onl/apim/portalinquiry/portalInquiryMan.view" />';
|
||||
var file_download = '<c:url value="/file/download.do" />';
|
||||
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 @@
|
||||
|
||||
<!-- 답변 작성 부분 -->
|
||||
<div class="response-section">
|
||||
<div class="info-row">
|
||||
<div class="info-label">상태</div>
|
||||
<div id="inquiryStatus" class="info-content"></div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">답변자</div>
|
||||
<div id="responder" class="info-content"></div>
|
||||
|
||||
-15
@@ -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<InquiryComment, String> {
|
||||
|
||||
long countByInquiry_Id(String inquiryId);
|
||||
|
||||
List<InquiryComment> findByInquiry_IdAndDelYnOrderBySeqAsc(String inquiryId, String delYn);
|
||||
}
|
||||
+28
@@ -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<InquiryComment, String> {
|
||||
|
||||
long countByInquiry_Id(String inquiryId);
|
||||
|
||||
long countByInquiry_IdAndDelYn(String inquiryId, String delYn);
|
||||
|
||||
List<InquiryComment> findByInquiry_IdAndDelYnOrderByCreatedDateAsc(String inquiryId, String delYn);
|
||||
|
||||
Optional<InquiryComment> findTopByInquiry_IdAndDelYnOrderByCreatedDateDesc(
|
||||
String inquiryId, String delYn);
|
||||
|
||||
Optional<InquiryComment> findTopByInquiry_IdAndAdminYnAndDelYnOrderByCreatedDateDesc(
|
||||
String inquiryId, String adminYn, String delYn);
|
||||
|
||||
boolean existsByInquiry_IdAndAdminYnAndDelYnAndCreatedDateAfter(
|
||||
String inquiryId, String adminYn, String delYn, LocalDateTime after);
|
||||
}
|
||||
+7
-3
@@ -10,18 +10,22 @@ import java.util.List;
|
||||
|
||||
@Service
|
||||
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||
public class PortalInquiryCommentService extends AbstractEMSDataSerivce<InquiryComment, String, InquiryCommentRepository> {
|
||||
public class PortalInquiryCommentService extends AbstractEMSDataSerivce<InquiryComment, String, PortalInquiryCommentRepository> {
|
||||
|
||||
// AbstractEMSDataSerivce의 repository 필드가 BaseRepository<T,I>로 고정되어
|
||||
// 커스텀 메서드를 호출할 수 없으므로 별도 필드로 직접 주입
|
||||
@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<InquiryComment> findByInquiryId(String inquiryId) {
|
||||
return inquiryCommentRepository.findByInquiry_IdAndDelYnOrderBySeqAsc(inquiryId, "N");
|
||||
return inquiryCommentRepository.findByInquiry_IdAndDelYnOrderByCreatedDateAsc(inquiryId, "N");
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -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<Inquiry, String> {
|
||||
|
||||
List<Inquiry> findByInquiryStatus(String inquiryStatus);
|
||||
}
|
||||
|
||||
+18
-6
@@ -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<Inquiry, String, PortalInquiryRepository> {
|
||||
|
||||
// AbstractEMSDataSerivce의 repository 필드가 BaseRepository<T,I>로 고정되어
|
||||
// 커스텀 메서드를 호출할 수 없으므로 별도 필드로 직접 주입
|
||||
@Autowired
|
||||
private PortalInquiryRepository portalInquiryRepository;
|
||||
|
||||
public Page<Inquiry> findAll(Pageable pageable, PortalInquirySearch portalInquirySearch) {
|
||||
QInquiry qInquiry = QInquiry.inquiry;
|
||||
|
||||
@@ -58,4 +66,8 @@ public class PortalInquiryService extends AbstractEMSDataSerivce<Inquiry, String
|
||||
repository.deleteAll(inquiries);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Inquiry> findByInquiryStatus(String inquiryStatus) {
|
||||
return portalInquiryRepository.findByInquiryStatus(inquiryStatus);
|
||||
}
|
||||
}
|
||||
@@ -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 상태 문의글 자동 종료 배치
|
||||
*
|
||||
* <p>종료 조건 (기본 7일):</p>
|
||||
* <ul>
|
||||
* <li>답변완료 후 댓글이 7일간 없는 경우</li>
|
||||
* <li>관리자 댓글 등록 후 7일간 포탈사용자 댓글이 없는 경우</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Cron: 0 5 0 * * ?</p>
|
||||
*
|
||||
* <p>Job 파라미터 (JobDataMap):</p>
|
||||
* <ul>
|
||||
* <li>{@value #KEY_INQUIRY_COMMENT_CLOSING_DAY}: 종료 기준 일수 (기본값: 7)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@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"));
|
||||
}
|
||||
}
|
||||
+78
@@ -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<Inquiry> 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<InquiryComment> 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);
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -20,9 +20,7 @@ public class PortalInquiryCommentUI {
|
||||
private String inquiryId;
|
||||
|
||||
private String commentDetail;
|
||||
|
||||
private int seq;
|
||||
|
||||
|
||||
private String delYn;
|
||||
|
||||
private String createdBy;
|
||||
|
||||
+13
-2
@@ -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("답변종료되어 수정할 수 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,4 +55,6 @@ public class PortalInquiryUI {
|
||||
private LocalDateTime lastModifiedDate;
|
||||
|
||||
private String orgName;
|
||||
|
||||
private int commentCount;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user