Q&A 7일후 자동 종료 배치
This commit is contained in:
-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