From e8076a8550bc8260d19276ef72519bab0907857e Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Fri, 13 Mar 2026 21:03:27 +0900 Subject: [PATCH] =?UTF-8?q?fix(purchase):=20=EC=9E=91=EC=97=85=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=82=AD=EC=A0=9C=20=EC=8B=9C=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=BA=90=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=20=EC=82=AD=EC=A0=9C=20(admin=20=EC=A0=84?= =?UTF-8?q?=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 삭제 권한을 admin 전용으로 변경 (requireAdmin) - 트랜잭션으로 reports → checkins → safety_education → schedule 순서 삭제 - 프론트엔드: admin만 삭제 버튼 표시, 종속 데이터 삭제 경고 추가 - 404 처리 및 한국어 에러 메시지 Co-Authored-By: Claude Opus 4.6 --- .../api/controllers/scheduleController.js | 9 ++++++--- tkpurchase/api/models/scheduleModel.js | 19 ++++++++++++++++++- tkpurchase/api/routes/scheduleRoutes.js | 4 ++-- .../web/static/js/tkpurchase-schedule.js | 7 ++++--- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/tkpurchase/api/controllers/scheduleController.js b/tkpurchase/api/controllers/scheduleController.js index 7480aaf..7a75eda 100644 --- a/tkpurchase/api/controllers/scheduleController.js +++ b/tkpurchase/api/controllers/scheduleController.js @@ -198,14 +198,17 @@ async function updateStatus(req, res) { } } -// 일정 삭제 +// 일정 삭제 (admin 전용, 관련 데이터 캐스케이드 삭제) async function deleteSchedule(req, res) { 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: '삭제 완료' }); } catch (err) { console.error('Schedule delete error:', err); - res.status(500).json({ success: false, error: err.message }); + res.status(500).json({ success: false, error: '일정 삭제 중 오류가 발생했습니다' }); } } diff --git a/tkpurchase/api/models/scheduleModel.js b/tkpurchase/api/models/scheduleModel.js index fc01e0c..9e360c5 100644 --- a/tkpurchase/api/models/scheduleModel.js +++ b/tkpurchase/api/models/scheduleModel.js @@ -113,8 +113,25 @@ async function updateStatus(id, status) { } async function deleteSchedule(id) { + const schedule = await findById(id); + if (!schedule) return null; + 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) { diff --git a/tkpurchase/api/routes/scheduleRoutes.js b/tkpurchase/api/routes/scheduleRoutes.js index da4da61..51a4dd6 100644 --- a/tkpurchase/api/routes/scheduleRoutes.js +++ b/tkpurchase/api/routes/scheduleRoutes.js @@ -1,6 +1,6 @@ const express = require('express'); const router = express.Router(); -const { requireAuth, requirePage } = require('../middleware/auth'); +const { requireAuth, requireAdmin, requirePage } = require('../middleware/auth'); const ctrl = require('../controllers/scheduleController'); 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/approve', requirePage('purchasing_schedule'), ctrl.approveRequest); 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; diff --git a/tkpurchase/web/static/js/tkpurchase-schedule.js b/tkpurchase/web/static/js/tkpurchase-schedule.js index 9652b13..bc6029d 100644 --- a/tkpurchase/web/static/js/tkpurchase-schedule.js +++ b/tkpurchase/web/static/js/tkpurchase-schedule.js @@ -77,6 +77,7 @@ function renderScheduleTable(list, total) { tbody.innerHTML = list.map(s => { 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 isRequest = s.status === 'requested'; 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) { ${isRequest ? `` : ''} ${(s.status === 'in_progress' || s.status === 'completed') ? `` : ''} - ${canEdit ? ` - ` : ''} + ${canEdit ? `` : ''} + ${isAdmin ? `` : ''} `; }).join(''); @@ -285,7 +286,7 @@ async function submitEditSchedule(e) { /* ===== Delete Schedule ===== */ async function deleteSchedule(id) { - if (!confirm('이 일정을 삭제하시겠습니까?')) return; + if (!confirm('이 일정을 삭제하시겠습니까?\n\n⚠️ 관련 체크인·작업보고서·안전교육 기록이 모두 삭제됩니다.')) return; try { await api('/schedules/' + id, { method: 'DELETE' }); showToast('일정이 삭제되었습니다');