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:
@@ -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 };
|
||||
|
||||
@@ -65,6 +65,11 @@ async function update(id, data) {
|
||||
return findById(id);
|
||||
}
|
||||
|
||||
async function resetCheckout(id) {
|
||||
const db = getPool();
|
||||
await db.query('UPDATE partner_work_checkins SET check_out_time = NULL WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
async function countActive() {
|
||||
const db = getPool();
|
||||
const [rows] = await db.query(
|
||||
@@ -73,4 +78,4 @@ async function countActive() {
|
||||
return rows[0].cnt;
|
||||
}
|
||||
|
||||
module.exports = { findBySchedule, findById, findTodayByCompany, checkIn, checkOut, update, countActive };
|
||||
module.exports = { findBySchedule, findById, findTodayByCompany, checkIn, checkOut, update, resetCheckout, countActive };
|
||||
|
||||
@@ -3,12 +3,14 @@ const { getPool } = require('./partnerModel');
|
||||
async function findAll({ company_id, date_from, date_to, schedule_id, confirmed, page = 1, limit = 50 } = {}) {
|
||||
const db = getPool();
|
||||
let sql = `SELECT wr.*, pc.company_name, ps.work_description AS schedule_description,
|
||||
su_reporter.name AS reporter_name, su_confirmer.name AS confirmed_by_name
|
||||
su_reporter.name AS reporter_name, su_confirmer.name AS confirmed_by_name,
|
||||
su_rejector.name AS rejected_by_name
|
||||
FROM partner_work_reports wr
|
||||
LEFT JOIN partner_companies pc ON wr.company_id = pc.id
|
||||
LEFT JOIN partner_schedules ps ON wr.schedule_id = ps.id
|
||||
LEFT JOIN sso_users su_reporter ON wr.reporter_id = su_reporter.user_id
|
||||
LEFT JOIN sso_users su_confirmer ON wr.confirmed_by = su_confirmer.user_id
|
||||
LEFT JOIN sso_users su_rejector ON wr.rejected_by = su_rejector.user_id
|
||||
WHERE 1=1`;
|
||||
const params = [];
|
||||
if (company_id) { sql += ' AND wr.company_id = ?'; params.push(company_id); }
|
||||
@@ -29,12 +31,14 @@ async function findById(id) {
|
||||
const db = getPool();
|
||||
const [rows] = await db.query(
|
||||
`SELECT wr.*, pc.company_name, ps.work_description AS schedule_description,
|
||||
su_reporter.name AS reporter_name, su_confirmer.name AS confirmed_by_name
|
||||
su_reporter.name AS reporter_name, su_confirmer.name AS confirmed_by_name,
|
||||
su_rejector.name AS rejected_by_name
|
||||
FROM partner_work_reports wr
|
||||
LEFT JOIN partner_companies pc ON wr.company_id = pc.id
|
||||
LEFT JOIN partner_schedules ps ON wr.schedule_id = ps.id
|
||||
LEFT JOIN sso_users su_reporter ON wr.reporter_id = su_reporter.user_id
|
||||
LEFT JOIN sso_users su_confirmer ON wr.confirmed_by = su_confirmer.user_id
|
||||
LEFT JOIN sso_users su_rejector ON wr.rejected_by = su_rejector.user_id
|
||||
WHERE wr.id = ?`, [id]);
|
||||
const report = rows[0] || null;
|
||||
if (report) {
|
||||
@@ -133,10 +137,11 @@ async function update(id, data) {
|
||||
if (data.issues !== undefined) { fields.push('issues = ?'); values.push(data.issues || null); }
|
||||
if (data.next_plan !== undefined) { fields.push('next_plan = ?'); values.push(data.next_plan || null); }
|
||||
|
||||
if (fields.length > 0) {
|
||||
values.push(id);
|
||||
await conn.query(`UPDATE partner_work_reports SET ${fields.join(', ')} WHERE id = ?`, values);
|
||||
}
|
||||
// 수정(재제출) 시 반려 상태 자동 해제
|
||||
fields.push('rejected_by = NULL', 'rejected_at = NULL', 'rejection_reason = NULL');
|
||||
|
||||
values.push(id);
|
||||
await conn.query(`UPDATE partner_work_reports SET ${fields.join(', ')} WHERE id = ?`, values);
|
||||
|
||||
// workers 교체 (있으면)
|
||||
if (data.workers !== undefined) {
|
||||
@@ -164,11 +169,19 @@ async function update(id, data) {
|
||||
async function confirm(id, confirmedBy) {
|
||||
const db = getPool();
|
||||
await db.query(
|
||||
'UPDATE partner_work_reports SET confirmed_by = ?, confirmed_at = NOW() WHERE id = ? AND confirmed_by IS NULL',
|
||||
'UPDATE partner_work_reports SET confirmed_by = ?, confirmed_at = NOW() WHERE id = ? AND confirmed_by IS NULL AND rejected_by IS NULL',
|
||||
[confirmedBy, id]);
|
||||
return findById(id);
|
||||
}
|
||||
|
||||
async function reject(id, rejectedBy, reason) {
|
||||
const db = getPool();
|
||||
await db.query(
|
||||
'UPDATE partner_work_reports SET rejected_by = ?, rejected_at = NOW(), rejection_reason = ? WHERE id = ? AND confirmed_by IS NULL',
|
||||
[rejectedBy, reason, id]);
|
||||
return findById(id);
|
||||
}
|
||||
|
||||
async function findAllAggregated({ company_id, schedule_id, date_from, date_to } = {}) {
|
||||
const db = getPool();
|
||||
let sql = `SELECT
|
||||
@@ -234,4 +247,16 @@ async function exportData({ company_id, schedule_id, date_from, date_to } = {})
|
||||
return rows;
|
||||
}
|
||||
|
||||
module.exports = { findAll, findById, findByCheckin, countByCheckin, create, update, confirm, findAllAggregated, exportData };
|
||||
async function deleteReport(id) {
|
||||
const db = getPool();
|
||||
await db.query('DELETE FROM partner_work_reports WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
async function unconfirm(id) {
|
||||
const db = getPool();
|
||||
await db.query(
|
||||
'UPDATE partner_work_reports SET confirmed_by = NULL, confirmed_at = NULL WHERE id = ?', [id]);
|
||||
return findById(id);
|
||||
}
|
||||
|
||||
module.exports = { findAll, findById, findByCheckin, countByCheckin, create, update, confirm, reject, deleteReport, unconfirm, findAllAggregated, exportData };
|
||||
|
||||
@@ -13,5 +13,8 @@ router.get('/:id', ctrl.getById);
|
||||
router.post('/', ctrl.create); // partner can create
|
||||
router.put('/:id', ctrl.update);
|
||||
router.put('/:id/confirm', requirePage('purchasing_workreport'), ctrl.confirm);
|
||||
router.put('/:id/reject', requirePage('purchasing_workreport'), ctrl.reject);
|
||||
router.put('/:id/unconfirm', requirePage('purchasing_workreport'), ctrl.unconfirm);
|
||||
router.delete('/:id', requirePage('purchasing_workreport'), ctrl.deleteReport);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user