- DjbApistatusIncidentPrenotice Entity/Repository 추가 - 사전 발송 시각 및 결과 관리 로직 PortalNoticeManService에 추가 - UI에 사전 발송 관련 Feature 추가 및 Validation 포함
This commit is contained in:
@@ -15,6 +15,14 @@
|
|||||||
<jsp:include page="/jsp/common/include/css.jsp"/>
|
<jsp:include page="/jsp/common/include/css.jsp"/>
|
||||||
<link rel="stylesheet" type="text/css" href="<c:url value="/css/editor-content.css"/>"/>
|
<link rel="stylesheet" type="text/css" href="<c:url value="/css/editor-content.css"/>"/>
|
||||||
<jsp:include page="/jsp/common/include/script.jsp"/>
|
<jsp:include page="/jsp/common/include/script.jsp"/>
|
||||||
|
<style>
|
||||||
|
/* 항목별 ? 도움말 — 유량제어 화면(inflowGroupDetailStyle.jsp)의 규칙을 가져오되 hover 가 아닌 클릭 토글 */
|
||||||
|
.hint-icon { display:inline-flex; align-items:center; cursor:pointer; color:#999; position:relative; margin-left:4px; vertical-align:middle; }
|
||||||
|
.hint-icon:hover { color:rgba(0,128,128,1); }
|
||||||
|
.hint-icon i { font-size:14px; }
|
||||||
|
.hint-icon .hint-bubble { display:none; position:absolute; bottom:calc(100% + 8px); left:0; background:#333; color:#fff; padding:10px 12px; border-radius:4px; font-size:11px; width:220px; line-height:1.5; z-index:1000; box-shadow:0 2px 8px rgba(0,0,0,0.2); white-space:normal; word-break:keep-all; text-align:left; font-weight:normal; }
|
||||||
|
.hint-icon .hint-bubble::after { content:''; position:absolute; top:100%; left:14px; border:6px solid transparent; border-top-color:#333; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<script language="javascript" >
|
<script language="javascript" >
|
||||||
var url = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.json" />';
|
var url = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.json" />';
|
||||||
@@ -32,6 +40,9 @@
|
|||||||
// 영향 API 목록 (UI 상태)
|
// 영향 API 목록 (UI 상태)
|
||||||
var affectedApis = [];
|
var affectedApis = [];
|
||||||
|
|
||||||
|
// 상세 진입 시 읽어온 사전 발송 지정 시각 (변경 여부 판단용)
|
||||||
|
var loadedPrenotiAt = '';
|
||||||
|
|
||||||
// 장애 타임라인 (UI 상태)
|
// 장애 타임라인 (UI 상태)
|
||||||
var currentIncidentId = null;
|
var currentIncidentId = null;
|
||||||
var currentIncidentState = null;
|
var currentIncidentState = null;
|
||||||
@@ -48,6 +59,13 @@
|
|||||||
'CANCELED': '취소'
|
'CANCELED': '취소'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// PrenoticeResult 라벨 — 사전 발송 결과
|
||||||
|
var PRENOTI_LABELS = {
|
||||||
|
'PENDING': '대기',
|
||||||
|
'SUCCESS': '성공',
|
||||||
|
'FAIL': '실패'
|
||||||
|
};
|
||||||
|
|
||||||
function isIncidentType(t) {
|
function isIncidentType(t) {
|
||||||
return t === NOTICE_TYPE_INCIDENT || t === NOTICE_TYPE_MAINTENANCE;
|
return t === NOTICE_TYPE_INCIDENT || t === NOTICE_TYPE_MAINTENANCE;
|
||||||
}
|
}
|
||||||
@@ -61,6 +79,51 @@
|
|||||||
return $('<div>').text(text == null ? '' : text).html();
|
return $('<div>').text(text == null ? '' : text).html();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 현재 시각을 datetime-local 입력값과 같은 'YYYY-MM-DDTHH:mm' 로 (문자열 비교용, 로컬 타임존 기준) */
|
||||||
|
function nowAsDatetimeLocal() {
|
||||||
|
var d = new Date();
|
||||||
|
function pad(n) { return (n < 10 ? '0' : '') + n; }
|
||||||
|
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
|
||||||
|
+ 'T' + pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사전 발송 결과 영역을 그린다.
|
||||||
|
*
|
||||||
|
* 결과 코드가 없으면(= 예약 없음) 결과는 '-' 로 두고 상세 박스를 감춘다.
|
||||||
|
* 실제 발송 시각·응답은 Webhook 발송 내역에서 확인해야 하므로 여기서는 작업 시각만 보여준다.
|
||||||
|
*/
|
||||||
|
function renderPrenoticeResult(data) {
|
||||||
|
var result = data.prenotiResult;
|
||||||
|
if (!result) {
|
||||||
|
$('#prenotiResultText').text('-');
|
||||||
|
$('#prenotiDetailBox').hide();
|
||||||
|
$('#prenotiFailRow').hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#prenotiResultText').text(PRENOTI_LABELS[result] || result);
|
||||||
|
|
||||||
|
if (result === 'PENDING') {
|
||||||
|
$('#prenotiDetailBox').hide();
|
||||||
|
$('#prenotiFailRow').hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 서버는 'yyyy-MM-ddTHH:mm' 으로 내려준다 (PortalNoticeUI.prenotiRunAt)
|
||||||
|
$('#prenotiRunAt').text((data.prenotiRunAt || '').replace('T', ' ') || '-');
|
||||||
|
$('#prenotiSentCnt').text(data.prenotiSentCnt == null ? '-' : data.prenotiSentCnt);
|
||||||
|
$('#prenotiTargetCnt').text(data.prenotiTargetCnt == null ? '-' : data.prenotiTargetCnt);
|
||||||
|
$('#prenotiDetailBox').show();
|
||||||
|
|
||||||
|
if (result === 'FAIL' && data.prenotiFailMsg) {
|
||||||
|
$('#prenotiFailMsg').text(data.prenotiFailMsg);
|
||||||
|
$('#prenotiFailRow').show();
|
||||||
|
} else {
|
||||||
|
$('#prenotiFailRow').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
var key = "${param.id}";
|
var key = "${param.id}";
|
||||||
isDetail = key != "" && key != "null";
|
isDetail = key != "" && key != "null";
|
||||||
@@ -118,6 +181,13 @@
|
|||||||
$('.incident-endat-hint').hide();
|
$('.incident-endat-hint').hide();
|
||||||
$('#endAt').prop('readonly', false);
|
$('#endAt').prop('readonly', false);
|
||||||
}
|
}
|
||||||
|
// 사전 발송은 점검 공지에만 있다 - 장애는 언제 터질지 모르니 미리 알릴 수 없다
|
||||||
|
$('.maintenance-row').toggle(t === NOTICE_TYPE_MAINTENANCE);
|
||||||
|
if (t !== NOTICE_TYPE_MAINTENANCE) {
|
||||||
|
// 유형을 바꿔 저장할 때 값이 딸려가지 않도록 비운다
|
||||||
|
$('#prenotiAt').val('');
|
||||||
|
}
|
||||||
|
|
||||||
// 양식(템플릿)은 장애 공지에만 쓴다 - 점검은 문구 체계가 다르다
|
// 양식(템플릿)은 장애 공지에만 쓴다 - 점검은 문구 체계가 다르다
|
||||||
$('.template-row').toggle(t === NOTICE_TYPE_INCIDENT);
|
$('.template-row').toggle(t === NOTICE_TYPE_INCIDENT);
|
||||||
|
|
||||||
@@ -430,6 +500,12 @@
|
|||||||
$('#summary').val(data.summary || '');
|
$('#summary').val(data.summary || '');
|
||||||
$('#startedAt').val(data.startedAt || '');
|
$('#startedAt').val(data.startedAt || '');
|
||||||
$('#endAt').val(data.endAt || '');
|
$('#endAt').val(data.endAt || '');
|
||||||
|
// 사전 발송 — 발송 작업이 이미 돈 건은 지정 시각을 잠근다
|
||||||
|
loadedPrenotiAt = data.prenotiAt || '';
|
||||||
|
$('#prenotiAt').val(loadedPrenotiAt);
|
||||||
|
$('#prenotiAt').prop('readonly', !!data.prenotiSent);
|
||||||
|
$('#prenotiLockHint').toggle(!!data.prenotiSent);
|
||||||
|
renderPrenoticeResult(data);
|
||||||
// 상태/이전 상태는 타임라인 게시로만 바뀐다 (직접 수정 불가)
|
// 상태/이전 상태는 타임라인 게시로만 바뀐다 (직접 수정 불가)
|
||||||
$('#stateText').text(data.state ? stateLabel(data.state) : '');
|
$('#stateText').text(data.state ? stateLabel(data.state) : '');
|
||||||
$('#previousState').text(data.previousState ? stateLabel(data.previousState) : '없음');
|
$('#previousState').text(data.previousState ? stateLabel(data.previousState) : '없음');
|
||||||
@@ -469,6 +545,15 @@
|
|||||||
|
|
||||||
$('#noticeType').on('change', toggleIncidentFields);
|
$('#noticeType').on('change', toggleIncidentFields);
|
||||||
|
|
||||||
|
// ? 도움말 — 클릭으로 열고, 바깥을 누르면 닫는다
|
||||||
|
$(document).on('click', '.hint-icon', function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
var bubble = $(this).find('.hint-bubble');
|
||||||
|
$('.hint-bubble').not(bubble).hide();
|
||||||
|
bubble.toggle();
|
||||||
|
});
|
||||||
|
$(document).on('click', function () { $('.hint-bubble').hide(); });
|
||||||
|
|
||||||
// 신규 등록에서 장애를 고르면 빈 본문에 한해 양식을 자동으로 채운다.
|
// 신규 등록에서 장애를 고르면 빈 본문에 한해 양식을 자동으로 채운다.
|
||||||
// (수정 모드나 이미 쓴 본문은 건드리지 않는다 - 덮어쓰려면 '양식 적용' 버튼)
|
// (수정 모드나 이미 쓴 본문은 건드리지 않는다 - 덮어쓰려면 '양식 적용' 버튼)
|
||||||
$('#noticeType').on('change', function () {
|
$('#noticeType').on('change', function () {
|
||||||
@@ -527,6 +612,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 사전 발송 (점검 한정) — 입력했을 때만 검사한다. 미입력이면 예약 없음.
|
||||||
|
if (noticeType === NOTICE_TYPE_MAINTENANCE && $('#prenotiAt').val()) {
|
||||||
|
if ($('#prenotiAt').val() >= $('#startedAt').val()) {
|
||||||
|
alert('사전 발송 시각은 점검 시작 시각보다 빨라야 합니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 지난 시각으로는 새로 예약할 수 없다 - 스케줄러가 곧바로 집어가 발송해 버린다.
|
||||||
|
// 값을 바꾸지 않았다면(= 스케줄러가 아직 못 집어간 기존 예약) 통과시킨다.
|
||||||
|
if ($('#prenotiAt').val() !== loadedPrenotiAt
|
||||||
|
&& $('#prenotiAt').val() <= nowAsDatetimeLocal()) {
|
||||||
|
alert('사전 발송 시각은 현재 시각 이후여야 합니다.\n이미 지난 시각으로는 예약할 수 없습니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (confirm("<%= localeMessage.getString("common.checkSave")%>") != true) return;
|
if (confirm("<%= localeMessage.getString("common.checkSave")%>") != true) return;
|
||||||
|
|
||||||
var formData = new FormData($("#ajaxForm")[0]);
|
var formData = new FormData($("#ajaxForm")[0]);
|
||||||
@@ -541,6 +641,7 @@
|
|||||||
// 시작/종료 시각이 비어 있으면 전송하지 않음 (서버 파싱 오류 방지)
|
// 시작/종료 시각이 비어 있으면 전송하지 않음 (서버 파싱 오류 방지)
|
||||||
if (!$('#startedAt').val()) formData.delete('startedAt');
|
if (!$('#startedAt').val()) formData.delete('startedAt');
|
||||||
if (!$('#endAt').val()) formData.delete('endAt');
|
if (!$('#endAt').val()) formData.delete('endAt');
|
||||||
|
if (!$('#prenotiAt').val()) formData.delete('prenotiAt');
|
||||||
|
|
||||||
// 영향 API → affectedApis[i].apiId/apiName 으로 전송
|
// 영향 API → affectedApis[i].apiId/apiName 으로 전송
|
||||||
$.each(affectedApis, function(i, api) {
|
$.each(affectedApis, function(i, api) {
|
||||||
@@ -656,6 +757,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<!-- ───── 점검 전용 - 사전 발송 ───── -->
|
||||||
|
<tr class="maintenance-row" style="display:none;">
|
||||||
|
<th>사전 발송</th>
|
||||||
|
<td>
|
||||||
|
<input type="datetime-local" id="prenotiAt" name="prenotiAt" style="width:90%" placeholder="미입력시 사전 발송 안함">
|
||||||
|
<div id="prenotiLockHint" style="display:none;color:#888;font-size:12px;margin-top:3px;">
|
||||||
|
※ 이미 발송된 건은 지정 시각을 변경할 수 없습니다.
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<th>발송 결과
|
||||||
|
<span class="hint-icon"><i class="material-icons">help_outline</i>
|
||||||
|
<span class="hint-bubble">여기 표시되는 값은 발송 작업이 돌아간 시각입니다.
|
||||||
|
실제 발송 시각과 응답은 Webhook 발송 내역에서 확인하여 주십시오.</span>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<span id="prenotiResultText">-</span>
|
||||||
|
<div id="prenotiDetailBox" style="display:none;color:#666;font-size:12px;margin-top:3px;">
|
||||||
|
<div>발송 작업 : <span id="prenotiRunAt"></span></div>
|
||||||
|
<div>발송 성공 대상 : <span id="prenotiSentCnt"></span> / 대상 <span id="prenotiTargetCnt"></span> 건</div>
|
||||||
|
<div id="prenotiFailRow" style="display:none;color:#c00;">실패 사유 : <span id="prenotiFailMsg"></span></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<!-- 상태/이전 상태는 타임라인 게시로만 전이한다 (직접 수정 불가) -->
|
<!-- 상태/이전 상태는 타임라인 게시로만 전이한다 (직접 수정 불가) -->
|
||||||
<tr class="incident-row incident-state-row" style="display:none;">
|
<tr class="incident-row incident-state-row" style="display:none;">
|
||||||
<th>상태</th>
|
<th>상태</th>
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ package com.eactive.eai.rms.onl.apim.portalnotice;
|
|||||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident;
|
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident;
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi;
|
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi;
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApiId;
|
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApiId;
|
||||||
|
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentPrenotice;
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentTimeline;
|
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentTimeline;
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind;
|
import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind;
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentState;
|
import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentState;
|
||||||
|
import com.eactive.apim.portal.djb.apistatus.incident.entity.PrenoticeResult;
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository;
|
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository;
|
||||||
|
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentPrenoticeRepository;
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentRepository;
|
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentRepository;
|
||||||
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentTimelineRepository;
|
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentTimelineRepository;
|
||||||
import com.eactive.apim.portal.file.entity.FileDetail;
|
import com.eactive.apim.portal.file.entity.FileDetail;
|
||||||
@@ -53,6 +56,7 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
private final DjbApistatusIncidentRepository incidentRepository;
|
private final DjbApistatusIncidentRepository incidentRepository;
|
||||||
private final DjbApistatusIncidentApiRepository incidentApiRepository;
|
private final DjbApistatusIncidentApiRepository incidentApiRepository;
|
||||||
private final DjbApistatusIncidentTimelineRepository incidentTimelineRepository;
|
private final DjbApistatusIncidentTimelineRepository incidentTimelineRepository;
|
||||||
|
private final DjbApistatusIncidentPrenoticeRepository incidentPrenoticeRepository;
|
||||||
private final ApiStatusDraftTemplate draftTemplate;
|
private final ApiStatusDraftTemplate draftTemplate;
|
||||||
private final ApiStatusNoticeComposer noticeComposer;
|
private final ApiStatusNoticeComposer noticeComposer;
|
||||||
|
|
||||||
@@ -81,6 +85,7 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
DjbApistatusIncidentRepository incidentRepository,
|
DjbApistatusIncidentRepository incidentRepository,
|
||||||
DjbApistatusIncidentApiRepository incidentApiRepository,
|
DjbApistatusIncidentApiRepository incidentApiRepository,
|
||||||
DjbApistatusIncidentTimelineRepository incidentTimelineRepository,
|
DjbApistatusIncidentTimelineRepository incidentTimelineRepository,
|
||||||
|
DjbApistatusIncidentPrenoticeRepository incidentPrenoticeRepository,
|
||||||
ApiStatusDraftTemplate draftTemplate,
|
ApiStatusDraftTemplate draftTemplate,
|
||||||
ApiStatusNoticeComposer noticeComposer){
|
ApiStatusNoticeComposer noticeComposer){
|
||||||
this.portalNoticeService = portalNoticeService;
|
this.portalNoticeService = portalNoticeService;
|
||||||
@@ -89,6 +94,7 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
this.incidentRepository = incidentRepository;
|
this.incidentRepository = incidentRepository;
|
||||||
this.incidentApiRepository = incidentApiRepository;
|
this.incidentApiRepository = incidentApiRepository;
|
||||||
this.incidentTimelineRepository = incidentTimelineRepository;
|
this.incidentTimelineRepository = incidentTimelineRepository;
|
||||||
|
this.incidentPrenoticeRepository = incidentPrenoticeRepository;
|
||||||
this.draftTemplate = draftTemplate;
|
this.draftTemplate = draftTemplate;
|
||||||
this.noticeComposer = noticeComposer;
|
this.noticeComposer = noticeComposer;
|
||||||
}
|
}
|
||||||
@@ -221,6 +227,21 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
return vo;
|
return vo;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
ui.setAffectedApis(apis);
|
ui.setAffectedApis(apis);
|
||||||
|
|
||||||
|
populatePrenotice(incident, ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 사전 발송 예약/결과를 화면에 실어준다. 예약이 없으면 값은 모두 비워둔다. */
|
||||||
|
private void populatePrenotice(DjbApistatusIncident incident, PortalNoticeUI ui) {
|
||||||
|
incidentPrenoticeRepository.findById(incident.getIncidentId()).ifPresent(prenotice -> {
|
||||||
|
ui.setPrenotiAt(prenotice.getPrenotiAt());
|
||||||
|
ui.setPrenotiRunAt(prenotice.getRunAt());
|
||||||
|
ui.setPrenotiResult(prenotice.getResult() == null ? null : prenotice.getResult().name());
|
||||||
|
ui.setPrenotiTargetCnt(prenotice.getTargetCnt());
|
||||||
|
ui.setPrenotiSentCnt(prenotice.getSentCnt());
|
||||||
|
ui.setPrenotiFailMsg(prenotice.getFailMsg());
|
||||||
|
ui.setPrenotiSent(isPrenoticeSent(prenotice));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insert(PortalNoticeUI portalNoticeUI) throws IOException {
|
public void insert(PortalNoticeUI portalNoticeUI) throws IOException {
|
||||||
@@ -326,9 +347,79 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
persistPrenotice(portalNoticeUI, saved);
|
||||||
|
|
||||||
return saved;
|
return saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ───────────────────── 점검 사전발송 ─────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사전 발송 예약을 저장한다. 행이 있다 = 예약이 있다.
|
||||||
|
*
|
||||||
|
* <p>사전 발송은 점검(MAINTENANCE)에만 있다 - 장애는 언제 터질지 모르니 미리 알릴 수 없다.
|
||||||
|
* 지정 시각을 비우고 저장하면 예약 취소로 보고 행을 지우되, <b>이미 발송된 건은 남긴다</b>
|
||||||
|
* (발송 이력을 관리자가 지울 수 있으면 안 된다).</p>
|
||||||
|
*
|
||||||
|
* <p>실제 발송은 이 코드가 하지 않는다. RUN_AT/RESULT/건수/실패사유는 향후 스케줄러가 채운다.</p>
|
||||||
|
*/
|
||||||
|
/** 발송 작업이 이미 돌았는지. RESULT 가 비어 있는 행(구 데이터)은 미발송으로 본다. */
|
||||||
|
private static boolean isPrenoticeSent(DjbApistatusIncidentPrenotice prenotice) {
|
||||||
|
return prenotice != null && prenotice.getResult() != null && prenotice.getResult().isSent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistPrenotice(PortalNoticeUI portalNoticeUI, DjbApistatusIncident incident) {
|
||||||
|
DjbApistatusIncidentPrenotice prenotice =
|
||||||
|
incidentPrenoticeRepository.findById(incident.getIncidentId()).orElse(null);
|
||||||
|
|
||||||
|
if (incident.getKind() != IncidentKind.MAINTENANCE) {
|
||||||
|
// 점검 → 장애로 유형이 바뀐 경우. 미발송 예약만 정리한다.
|
||||||
|
if (prenotice != null && !isPrenoticeSent(prenotice)) {
|
||||||
|
incidentPrenoticeRepository.delete(prenotice);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime prenotiAt = portalNoticeUI.getPrenotiAt();
|
||||||
|
|
||||||
|
if (prenotiAt == null) {
|
||||||
|
if (prenotice != null && !isPrenoticeSent(prenotice)) {
|
||||||
|
incidentPrenoticeRepository.delete(prenotice);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이미 발송된 건은 지정 시각을 못 바꾼다. 다만 값이 그대로면 제목만 고치는 저장까지 막지는 않는다.
|
||||||
|
if (isPrenoticeSent(prenotice)) {
|
||||||
|
if (!prenotiAt.equals(prenotice.getPrenotiAt())) {
|
||||||
|
throw new IllegalArgumentException("이미 발송된 사전 발송은 지정 시각을 변경할 수 없습니다.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "사전"이므로 점검 시작보다 빨라야 한다 - 시작 이후면 예고가 아니라 사후 통보가 된다.
|
||||||
|
if (incident.getStartedAt() != null && !prenotiAt.isBefore(incident.getStartedAt())) {
|
||||||
|
throw new IllegalArgumentException("사전 발송 시각은 점검 시작 시각보다 빨라야 합니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 지난 시각으로는 새로 예약할 수 없다. 스케줄러는 'PENDING 이고 지정 시각이 지난' 건을 집어가므로,
|
||||||
|
// 이미 끝난 점검 공지를 수정하면서 과거 시각을 넣으면 곧바로 발송돼 버린다.
|
||||||
|
// 단, 값이 그대로면 통과시킨다 - 스케줄러가 아직 집어가지 못한 기존 예약 때문에
|
||||||
|
// 제목만 고치는 평범한 수정까지 막히면 안 된다.
|
||||||
|
boolean prenotiAtChanged = prenotice == null || !prenotiAt.equals(prenotice.getPrenotiAt());
|
||||||
|
if (prenotiAtChanged && !prenotiAt.isAfter(LocalDateTime.now())) {
|
||||||
|
throw new IllegalArgumentException("사전 발송 시각은 현재 시각 이후여야 합니다. 이미 지난 시각으로는 예약할 수 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prenotice == null) {
|
||||||
|
prenotice = new DjbApistatusIncidentPrenotice();
|
||||||
|
prenotice.setIncidentId(incident.getIncidentId());
|
||||||
|
prenotice.setResult(PrenoticeResult.PENDING);
|
||||||
|
}
|
||||||
|
prenotice.setPrenotiAt(prenotiAt);
|
||||||
|
incidentPrenoticeRepository.save(prenotice);
|
||||||
|
}
|
||||||
|
|
||||||
// ───────────────────── 장애 처리 타임라인 ─────────────────────
|
// ───────────────────── 장애 처리 타임라인 ─────────────────────
|
||||||
|
|
||||||
private static final String AUTHOR_OPERATOR = "OPERATOR";
|
private static final String AUTHOR_OPERATOR = "OPERATOR";
|
||||||
@@ -478,6 +569,7 @@ public class PortalNoticeManService extends BaseService {
|
|||||||
incidentRepository.findByNoticeId(noticeId).ifPresent(incident -> {
|
incidentRepository.findByNoticeId(noticeId).ifPresent(incident -> {
|
||||||
incidentApiRepository.deleteByIncidentId(incident.getIncidentId());
|
incidentApiRepository.deleteByIncidentId(incident.getIncidentId());
|
||||||
incidentTimelineRepository.deleteByIncidentId(incident.getIncidentId());
|
incidentTimelineRepository.deleteByIncidentId(incident.getIncidentId());
|
||||||
|
incidentPrenoticeRepository.deleteByIncidentId(incident.getIncidentId());
|
||||||
incidentRepository.delete(incident);
|
incidentRepository.delete(incident);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,4 +78,24 @@ public class PortalNoticeUI {
|
|||||||
private String previousState; // 직전 상태
|
private String previousState; // 직전 상태
|
||||||
|
|
||||||
private List<IncidentAffectedApiUI> affectedApis;
|
private List<IncidentAffectedApiUI> affectedApis;
|
||||||
|
|
||||||
|
// ───── 사전 발송 (점검 공지 한정, djb_apistatus_incident_prenotice) ─────
|
||||||
|
// 관리자가 입력하는 값은 prenotiAt 하나다. 나머지는 발송 스케줄러가 채우는 읽기 전용 결과.
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm")
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
|
||||||
|
private LocalDateTime prenotiAt;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm")
|
||||||
|
private LocalDateTime prenotiRunAt;
|
||||||
|
|
||||||
|
private String prenotiResult; // PENDING / SUCCESS / FAIL
|
||||||
|
|
||||||
|
private Integer prenotiTargetCnt;
|
||||||
|
|
||||||
|
private Integer prenotiSentCnt;
|
||||||
|
|
||||||
|
private String prenotiFailMsg;
|
||||||
|
|
||||||
|
/** 발송 작업이 이미 돌았는지 - 화면에서 지정 시각을 잠그는 판단에 쓴다. */
|
||||||
|
private boolean prenotiSent;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user