diff --git a/tkpurchase/api/controllers/checkinController.js b/tkpurchase/api/controllers/checkinController.js index 9f29520..27455c6 100644 --- a/tkpurchase/api/controllers/checkinController.js +++ b/tkpurchase/api/controllers/checkinController.js @@ -108,4 +108,16 @@ async function stats(req, res) { } } -module.exports = { list, myCheckins, checkIn, checkOut, update, stats }; +// 체크인 삭제 +async function deleteCheckin(req, res) { + try { + const result = await checkinModel.deleteCheckin(req.params.id); + if (!result) return res.status(404).json({ success: false, error: '체크인 기록을 찾을 수 없습니다' }); + res.json({ success: true }); + } catch (err) { + console.error('Checkin delete error:', err); + res.status(500).json({ success: false, error: err.message }); + } +} + +module.exports = { list, myCheckins, checkIn, checkOut, update, stats, deleteCheckin }; diff --git a/tkpurchase/api/models/checkinModel.js b/tkpurchase/api/models/checkinModel.js index b469862..d9f128c 100644 --- a/tkpurchase/api/models/checkinModel.js +++ b/tkpurchase/api/models/checkinModel.js @@ -78,4 +78,32 @@ async function countActive() { return rows[0].cnt; } -module.exports = { findBySchedule, findById, findTodayByCompany, checkIn, checkOut, update, resetCheckout, countActive }; +async function deleteCheckin(id) { + const checkin = await findById(id); + if (!checkin) return false; + + const db = getPool(); + const conn = await db.getConnection(); + try { + await conn.beginTransaction(); + await conn.query('DELETE FROM partner_work_reports WHERE checkin_id = ?', [id]); + await conn.query('DELETE FROM partner_work_checkins WHERE id = ?', [id]); + const [remaining] = await conn.query( + 'SELECT COUNT(*) AS cnt FROM partner_work_checkins WHERE schedule_id = ?', + [checkin.schedule_id]); + if (remaining[0].cnt === 0) { + await conn.query( + "UPDATE partner_schedules SET status = 'scheduled' WHERE id = ? AND status = 'in_progress'", + [checkin.schedule_id]); + } + await conn.commit(); + return true; + } catch (err) { + await conn.rollback(); + throw err; + } finally { + conn.release(); + } +} + +module.exports = { findBySchedule, findById, findTodayByCompany, checkIn, checkOut, update, resetCheckout, countActive, deleteCheckin }; diff --git a/tkpurchase/api/routes/checkinRoutes.js b/tkpurchase/api/routes/checkinRoutes.js index 204613e..f0193cc 100644 --- a/tkpurchase/api/routes/checkinRoutes.js +++ b/tkpurchase/api/routes/checkinRoutes.js @@ -1,6 +1,6 @@ const express = require('express'); const router = express.Router(); -const { requireAuth } = require('../middleware/auth'); +const { requireAuth, requirePage } = require('../middleware/auth'); const ctrl = require('../controllers/checkinController'); router.use(requireAuth); @@ -10,6 +10,7 @@ router.get('/schedule/:scheduleId', ctrl.list); router.get('/my', ctrl.myCheckins); // partner portal router.post('/', ctrl.checkIn); // partner can do this router.put('/:id/checkout', ctrl.checkOut); -router.put('/:id', ctrl.update); +router.put('/:id', requirePage('purchasing_schedule'), ctrl.update); +router.delete('/:id', requirePage('purchasing_schedule'), ctrl.deleteCheckin); module.exports = router; diff --git a/tkpurchase/web/schedule.html b/tkpurchase/web/schedule.html index 03fb35a..0b20ab7 100644 --- a/tkpurchase/web/schedule.html +++ b/tkpurchase/web/schedule.html @@ -211,8 +211,21 @@ - - + + + + + diff --git a/tkpurchase/web/static/js/tkpurchase-partner-portal.js b/tkpurchase/web/static/js/tkpurchase-partner-portal.js index 76f43e0..3182252 100644 --- a/tkpurchase/web/static/js/tkpurchase-partner-portal.js +++ b/tkpurchase/web/static/js/tkpurchase-partner-portal.js @@ -421,12 +421,15 @@ async function openEditReport(reportId, checkinId, scheduleId) { async function doCheckIn(scheduleId) { const workerCount = parseInt(document.getElementById('checkinWorkers_' + scheduleId).value) || 1; - const workerNames = document.getElementById('checkinNames_' + scheduleId).value.trim(); + const rawNames = document.getElementById('checkinNames_' + scheduleId).value.trim(); + const workerNames = rawNames + ? rawNames.split(/[,,]\s*/).map(n => n.trim()).filter(Boolean) + : null; const body = { schedule_id: scheduleId, actual_worker_count: workerCount, - worker_names: workerNames || null + worker_names: workerNames }; try { diff --git a/tkpurchase/web/static/js/tkpurchase-schedule.js b/tkpurchase/web/static/js/tkpurchase-schedule.js index 59c6447..e665bd2 100644 --- a/tkpurchase/web/static/js/tkpurchase-schedule.js +++ b/tkpurchase/web/static/js/tkpurchase-schedule.js @@ -86,6 +86,7 @@ function renderScheduleTable(list, total) { ${s.expected_workers || 0}명 ${label} + ${(s.status === 'in_progress' || s.status === 'completed') ? `` : ''} ${canEdit ? ` ` : ''} @@ -290,6 +291,83 @@ async function deleteSchedule(id) { } } +/* ===== Checkin Management ===== */ +async function viewCheckins(scheduleId) { + document.getElementById('checkinModal').classList.remove('hidden'); + const body = document.getElementById('checkinModalBody'); + body.innerHTML = '

로딩 중...

'; + + try { + const r = await api('/checkins/schedule/' + scheduleId); + const list = r.data || []; + if (!list.length) { + body.innerHTML = '

체크인 기록이 없습니다

'; + return; + } + body.innerHTML = list.map(c => { + let names = ''; + if (c.worker_names) { + try { + const parsed = JSON.parse(c.worker_names); + names = Array.isArray(parsed) ? parsed.join(', ') : parsed; + } catch { names = c.worker_names; } + } + const checkinTime = c.check_in_time ? new Date(c.check_in_time).toLocaleString('ko-KR', { month:'numeric', day:'numeric', hour:'2-digit', minute:'2-digit' }) : '-'; + const checkoutTime = c.check_out_time ? new Date(c.check_out_time).toLocaleString('ko-KR', { hour:'2-digit', minute:'2-digit' }) : '작업중'; + return `
+
+
+
${escapeHtml(c.company_name || '')} ${checkinTime} ~ ${checkoutTime}
+
인원: ${c.actual_worker_count || 0}명
+
작업자: ${escapeHtml(names) || '-'}
+
+
+ + +
+
+
`; + }).join(''); + } catch(e) { + body.innerHTML = '

로딩 실패

'; + } +} + +function closeCheckinModal() { + document.getElementById('checkinModal').classList.add('hidden'); +} + +async function deleteCheckin(checkinId, scheduleId) { + if (!confirm('이 체크인을 삭제하시겠습니까?\n관련 업무현황도 함께 삭제됩니다.')) return; + try { + await api('/checkins/' + checkinId, { method: 'DELETE' }); + showToast('체크인이 삭제되었습니다'); + viewCheckins(scheduleId); + loadSchedules(); + } catch(e) { + showToast(e.message || '삭제 실패', 'error'); + } +} + +async function editCheckinWorkers(checkinId) { + const el = document.getElementById('checkinNames_' + checkinId); + const current = el.textContent.replace('작업자: ', '').trim(); + const input = prompt('작업자 명단 (콤마로 구분)', current === '-' ? '' : current); + if (input === null) return; + + const names = input.split(/[,,]\s*/).map(n => n.trim()).filter(Boolean); + try { + await api('/checkins/' + checkinId, { + method: 'PUT', + body: JSON.stringify({ worker_names: names.length ? names : null }) + }); + el.textContent = '작업자: ' + (names.length ? names.join(', ') : '-'); + showToast('작업자 명단이 수정되었습니다'); + } catch(e) { + showToast(e.message || '수정 실패', 'error'); + } +} + /* ===== Init ===== */ function initSchedulePage() { if (!initAuth()) return;