fix(purchase): 작업일정 삭제 시 관련 데이터 캐스케이드 삭제 (admin 전용)
- 삭제 권한을 admin 전용으로 변경 (requireAdmin) - 트랜잭션으로 reports → checkins → safety_education → schedule 순서 삭제 - 프론트엔드: admin만 삭제 버튼 표시, 종속 데이터 삭제 경고 추가 - 404 처리 및 한국어 에러 메시지 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -198,14 +198,17 @@ async function updateStatus(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 일정 삭제
|
// 일정 삭제 (admin 전용, 관련 데이터 캐스케이드 삭제)
|
||||||
async function deleteSchedule(req, res) {
|
async function deleteSchedule(req, res) {
|
||||||
try {
|
try {
|
||||||
await scheduleModel.deleteSchedule(req.params.id);
|
const result = await scheduleModel.deleteSchedule(req.params.id);
|
||||||
|
if (result === null) {
|
||||||
|
return res.status(404).json({ success: false, error: '일정을 찾을 수 없습니다' });
|
||||||
|
}
|
||||||
res.json({ success: true, message: '삭제 완료' });
|
res.json({ success: true, message: '삭제 완료' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Schedule delete error:', err);
|
console.error('Schedule delete error:', err);
|
||||||
res.status(500).json({ success: false, error: err.message });
|
res.status(500).json({ success: false, error: '일정 삭제 중 오류가 발생했습니다' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,8 +113,25 @@ async function updateStatus(id, status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSchedule(id) {
|
async function deleteSchedule(id) {
|
||||||
|
const schedule = await findById(id);
|
||||||
|
if (!schedule) return null;
|
||||||
|
|
||||||
const db = getPool();
|
const db = getPool();
|
||||||
await db.query('DELETE FROM partner_schedules WHERE id = ?', [id]);
|
const conn = await db.getConnection();
|
||||||
|
try {
|
||||||
|
await conn.beginTransaction();
|
||||||
|
await conn.query('DELETE FROM partner_work_reports WHERE schedule_id = ?', [id]);
|
||||||
|
await conn.query('DELETE FROM partner_work_checkins WHERE schedule_id = ?', [id]);
|
||||||
|
await conn.query("DELETE FROM safety_education_reports WHERE target_type = 'partner_schedule' AND target_id = ?", [id]);
|
||||||
|
await conn.query('DELETE FROM partner_schedules WHERE id = ?', [id]);
|
||||||
|
await conn.commit();
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
await conn.rollback();
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
conn.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findActiveByCompany(companyId) {
|
async function findActiveByCompany(companyId) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { requireAuth, requirePage } = require('../middleware/auth');
|
const { requireAuth, requireAdmin, requirePage } = require('../middleware/auth');
|
||||||
const ctrl = require('../controllers/scheduleController');
|
const ctrl = require('../controllers/scheduleController');
|
||||||
|
|
||||||
router.use(requireAuth);
|
router.use(requireAuth);
|
||||||
@@ -14,6 +14,6 @@ router.put('/:id', requirePage('purchasing_schedule'), ctrl.update);
|
|||||||
router.put('/:id/status', requirePage('purchasing_schedule'), ctrl.updateStatus);
|
router.put('/:id/status', requirePage('purchasing_schedule'), ctrl.updateStatus);
|
||||||
router.put('/:id/approve', requirePage('purchasing_schedule'), ctrl.approveRequest);
|
router.put('/:id/approve', requirePage('purchasing_schedule'), ctrl.approveRequest);
|
||||||
router.put('/:id/reject', requirePage('purchasing_schedule'), ctrl.rejectRequest);
|
router.put('/:id/reject', requirePage('purchasing_schedule'), ctrl.rejectRequest);
|
||||||
router.delete('/:id', requirePage('purchasing_schedule'), ctrl.deleteSchedule);
|
router.delete('/:id', requireAdmin, ctrl.deleteSchedule);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ function renderScheduleTable(list, total) {
|
|||||||
|
|
||||||
tbody.innerHTML = list.map(s => {
|
tbody.innerHTML = list.map(s => {
|
||||||
const [cls, label] = statusMap[s.status] || ['badge-gray', s.status];
|
const [cls, label] = statusMap[s.status] || ['badge-gray', s.status];
|
||||||
|
const isAdmin = currentUser && (currentUser.role === 'admin' || currentUser.role === 'system');
|
||||||
const canEdit = s.status === 'scheduled';
|
const canEdit = s.status === 'scheduled';
|
||||||
const isRequest = s.status === 'requested';
|
const isRequest = s.status === 'requested';
|
||||||
const projectLabel = s.project_name ? (s.job_no ? `[${s.job_no}] ${s.project_name}` : s.project_name) : '';
|
const projectLabel = s.project_name ? (s.job_no ? `[${s.job_no}] ${s.project_name}` : s.project_name) : '';
|
||||||
@@ -91,8 +92,8 @@ function renderScheduleTable(list, total) {
|
|||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
${isRequest ? `<button onclick="openApproveModal(${s.id})" class="text-amber-600 hover:text-amber-800 text-xs mr-1 font-medium" title="승인/반려"><i class="fas fa-check-circle mr-1"></i>처리</button>` : ''}
|
${isRequest ? `<button onclick="openApproveModal(${s.id})" class="text-amber-600 hover:text-amber-800 text-xs mr-1 font-medium" title="승인/반려"><i class="fas fa-check-circle mr-1"></i>처리</button>` : ''}
|
||||||
${(s.status === 'in_progress' || s.status === 'completed') ? `<button onclick="viewCheckins(${s.id})" class="text-emerald-600 hover:text-emerald-800 text-xs mr-1" title="체크인 현황"><i class="fas fa-clipboard-check"></i></button>` : ''}
|
${(s.status === 'in_progress' || s.status === 'completed') ? `<button onclick="viewCheckins(${s.id})" class="text-emerald-600 hover:text-emerald-800 text-xs mr-1" title="체크인 현황"><i class="fas fa-clipboard-check"></i></button>` : ''}
|
||||||
${canEdit ? `<button onclick="openEditSchedule(${s.id})" class="text-blue-600 hover:text-blue-800 text-xs mr-1" title="수정"><i class="fas fa-edit"></i></button>
|
${canEdit ? `<button onclick="openEditSchedule(${s.id})" class="text-blue-600 hover:text-blue-800 text-xs mr-1" title="수정"><i class="fas fa-edit"></i></button>` : ''}
|
||||||
<button onclick="deleteSchedule(${s.id})" class="text-red-500 hover:text-red-700 text-xs" title="삭제"><i class="fas fa-trash"></i></button>` : ''}
|
${isAdmin ? `<button onclick="deleteSchedule(${s.id})" class="text-red-500 hover:text-red-700 text-xs" title="삭제"><i class="fas fa-trash"></i></button>` : ''}
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -285,7 +286,7 @@ async function submitEditSchedule(e) {
|
|||||||
|
|
||||||
/* ===== Delete Schedule ===== */
|
/* ===== Delete Schedule ===== */
|
||||||
async function deleteSchedule(id) {
|
async function deleteSchedule(id) {
|
||||||
if (!confirm('이 일정을 삭제하시겠습니까?')) return;
|
if (!confirm('이 일정을 삭제하시겠습니까?\n\n⚠️ 관련 체크인·작업보고서·안전교육 기록이 모두 삭제됩니다.')) return;
|
||||||
try {
|
try {
|
||||||
await api('/schedules/' + id, { method: 'DELETE' });
|
await api('/schedules/' + id, { method: 'DELETE' });
|
||||||
showToast('일정이 삭제되었습니다');
|
showToast('일정이 삭제되었습니다');
|
||||||
|
|||||||
Reference in New Issue
Block a user