feat: 안전 코드 tksafety 이관 + 사용자 관리 정리 + UI Tailwind 전환

Phase 1: tksafety에 출입신청/체크리스트 API·웹 추가, tkfb 안전 코드 삭제
Phase 2: 사용자 관리 페이지 삭제, API 축소, 알림 수신자 tkuser 이관
Phase 3: tkuser 권한 페이지 정의 업데이트
Phase 4: 전체 34개 페이지 Tailwind CSS + tkfb-core.js 전환,
         미사용 CSS 20개·인프라 JS 10개·템플릿·컴포넌트 삭제

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-13 10:46:22 +09:00
parent 8373fe9e75
commit 9fda89a374
133 changed files with 5255 additions and 26181 deletions

View File

@@ -110,8 +110,8 @@ async function update(req, res) {
return res.status(403).json({ success: false, error: '본인이 작성한 보고만 수정할 수 있습니다' });
}
// 확인 완료된 보고 수정 불가
if (existing.confirmed_by) {
// 확인 완료된 보고 수정 불가 (협력업체만 제한, 구매팀은 수정 가능)
if (req.user.partner_company_id && existing.confirmed_by) {
return res.status(400).json({ success: false, error: '확인 완료된 보고는 수정할 수 없습니다' });
}
@@ -136,6 +136,32 @@ async function confirm(req, res) {
}
}
// 작업보고 반려
async function reject(req, res) {
try {
const existing = await workReportModel.findById(req.params.id);
if (!existing) {
return res.status(404).json({ success: false, error: '작업보고를 찾을 수 없습니다' });
}
if (existing.confirmed_by) {
return res.status(400).json({ success: false, error: '확인된 보고는 반려할 수 없습니다. 먼저 확인 취소하세요' });
}
if (existing.rejected_by) {
return res.status(400).json({ success: false, error: '이미 반려된 보고입니다' });
}
const { reason } = req.body;
if (!reason || !reason.trim()) {
return res.status(400).json({ success: false, error: '반려 사유를 입력하세요' });
}
const rejectedBy = req.user.user_id || req.user.id;
const row = await workReportModel.reject(req.params.id, rejectedBy, reason.trim());
res.json({ success: true, data: row });
} catch (err) {
console.error('WorkReport reject error:', err);
res.status(500).json({ success: false, error: err.message });
}
}
// 종합 요약
async function summary(req, res) {
try {
@@ -252,4 +278,47 @@ async function exportExcel(req, res) {
}
}
module.exports = { list, getById, myReports, create, update, confirm, summary, exportExcel };
// 작업보고 삭제
async function deleteReport(req, res) {
try {
const existing = await workReportModel.findById(req.params.id);
if (!existing) {
return res.status(404).json({ success: false, error: '작업보고를 찾을 수 없습니다' });
}
const checkinId = existing.checkin_id;
await workReportModel.deleteReport(req.params.id);
// 남은 보고가 0건이면 체크아웃 되돌리기
if (checkinId) {
const remaining = await workReportModel.countByCheckin(checkinId);
if (remaining === 0) {
await checkinModel.resetCheckout(checkinId);
}
}
res.json({ success: true, message: '삭제되었습니다' });
} catch (err) {
console.error('WorkReport delete error:', err);
res.status(500).json({ success: false, error: err.message });
}
}
// 확인 취소
async function unconfirm(req, res) {
try {
const existing = await workReportModel.findById(req.params.id);
if (!existing) {
return res.status(404).json({ success: false, error: '작업보고를 찾을 수 없습니다' });
}
if (!existing.confirmed_by) {
return res.status(400).json({ success: false, error: '이미 미확인 상태입니다' });
}
const row = await workReportModel.unconfirm(req.params.id);
res.json({ success: true, data: row });
} catch (err) {
console.error('WorkReport unconfirm error:', err);
res.status(500).json({ success: false, error: err.message });
}
}
module.exports = { list, getById, myReports, create, update, confirm, reject, deleteReport, unconfirm, summary, exportExcel };