fix: 보고된 중간 점검일 삭제 방지(보고 이력 보존)

수정 시 보고가 접수된 점검일을 지우면 CASCADE 로 보고서까지 삭제되던 문제 방지:
- 백엔드 syncCheckpoints: 보고가 있는 점검일은 삭제 대상에서 제외
- 프론트 수정 폼: 보고된 점검일은 '보고됨' 잠금 배지로 표시하고 삭제 버튼 비노출

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 15:49:51 +09:00
parent 814dd7bb8b
commit b717ecd9a5
2 changed files with 43 additions and 2 deletions
+18 -2
View File
@@ -1383,7 +1383,12 @@ export class TaskService {
c.workStatus = ws;
changed.push(c);
}
return { itemId: c.id, text: c.text, category: c.category, workStatus: ws };
return {
itemId: c.id,
text: c.text,
category: c.category,
workStatus: ws,
};
});
if (changed.length) {
await this.checklistRepo.save(changed);
@@ -1429,8 +1434,19 @@ export class TaskService {
const existingTimes = existing.map((c) => c.checkAt.getTime());
const wantedTimes = [...new Set(dates.map((d) => new Date(d).getTime()))];
// 보고가 있는 점검일은 삭제하지 않는다 — 보고 이력(스냅샷) 보존
// (점검일 삭제 시 checkpoint_reports 가 CASCADE 로 함께 사라지므로 명시적으로 보호)
const reports = await this.reportRepo.find({
where: { task: { id: task.id } },
relations: ['checkpoint'],
});
const reportedIds = new Set(
reports.map((r) => r.checkpoint?.id).filter((id): id is string => !!id),
);
const toRemove = existing.filter(
(c) => !wantedTimes.includes(c.checkAt.getTime()),
(c) =>
!wantedTimes.includes(c.checkAt.getTime()) && !reportedIds.has(c.id),
);
if (toRemove.length) {
await this.checkpointRepo.remove(toRemove);
@@ -100,6 +100,13 @@ async function loadTaskForEdit() {
}))
existingFiles.value = t.files
checkpoints.value = t.checkpoints.map((c) => toDateInput(c.checkAt)).sort()
// 보고가 접수된 점검일은 잠금(삭제 불가) — 보고 이력 보존
const reportedCpIds = new Set(t.checkpointReports.map((r) => r.checkpointId))
reportedCheckpointDates.value = new Set(
t.checkpoints
.filter((c) => reportedCpIds.has(c.id))
.map((c) => toDateInput(c.checkAt)),
)
} catch {
// 404 등은 인터셉터가 알림 처리
}
@@ -163,6 +170,8 @@ const dueDate = ref('') // yyyy-MM-dd (input[type=date])
// --- 중간 점검일(여러 개) — 작업자가 진행 상태를 보고하는 기준일 ---
const checkpoints = ref<string[]>([]) // yyyy-MM-dd 목록
const newCheckpoint = ref('')
// 이미 보고가 접수된 점검일(yyyy-MM-dd) — 삭제 불가(보고 이력 보존)
const reportedCheckpointDates = ref<Set<string>>(new Set())
function addCheckpoint() {
const d = newCheckpoint.value
if (!d) return
@@ -173,6 +182,8 @@ function addCheckpoint() {
newCheckpoint.value = ''
}
function removeCheckpoint(d: string) {
// 보고가 접수된 날짜는 삭제하지 않는다(서버도 거부/보존)
if (reportedCheckpointDates.value.has(d)) return
checkpoints.value = checkpoints.value.filter((x) => x !== d)
}
@@ -1081,7 +1092,13 @@ function goBack() {
class="cp-item"
>
<span>{{ d }}</span>
<span
v-if="reportedCheckpointDates.has(d)"
class="cp-locked"
title="보고가 접수되어 삭제할 수 없습니다"
>보고됨</span>
<button
v-else
class="x"
type="button"
title="삭제"
@@ -2259,6 +2276,14 @@ function goBack() {
line-height: 1;
padding: 0 0.25rem;
}
.cp-locked {
font-size: 0.6875rem;
font-weight: 700;
color: #1b7a3d;
background: var(--green-weak);
padding: 0.0625rem 0.4375rem;
border-radius: 20px;
}
.cp-item .x:hover {
color: var(--red);
}