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:
@@ -1,146 +0,0 @@
|
||||
// models/notificationRecipientModel.js
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
const NOTIFICATION_TYPES = {
|
||||
repair: '설비 수리',
|
||||
safety: '안전 신고',
|
||||
nonconformity: '부적합 신고',
|
||||
equipment: '설비 관련',
|
||||
maintenance: '정기점검',
|
||||
system: '시스템'
|
||||
};
|
||||
|
||||
const notificationRecipientModel = {
|
||||
// 알림 유형 목록 가져오기
|
||||
getTypes() {
|
||||
return NOTIFICATION_TYPES;
|
||||
},
|
||||
|
||||
// 유형별 수신자 목록 조회
|
||||
async getByType(notificationType) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT nr.*, u.username, u.name as user_name, r.name as role
|
||||
FROM notification_recipients nr
|
||||
JOIN users u ON nr.user_id = u.user_id
|
||||
LEFT JOIN roles r ON u.role_id = r.id
|
||||
WHERE nr.notification_type = ? AND nr.is_active = 1
|
||||
ORDER BY u.name`,
|
||||
[notificationType]
|
||||
);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// 전체 수신자 목록 조회 (유형별 그룹화)
|
||||
async getAll() {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT nr.*, u.username, u.name as user_name, r.name as role
|
||||
FROM notification_recipients nr
|
||||
JOIN users u ON nr.user_id = u.user_id
|
||||
LEFT JOIN roles r ON u.role_id = r.id
|
||||
WHERE nr.is_active = 1
|
||||
ORDER BY nr.notification_type, u.name`
|
||||
);
|
||||
|
||||
// 유형별로 그룹화
|
||||
const grouped = {};
|
||||
for (const type in NOTIFICATION_TYPES) {
|
||||
grouped[type] = {
|
||||
label: NOTIFICATION_TYPES[type],
|
||||
recipients: []
|
||||
};
|
||||
}
|
||||
|
||||
rows.forEach(row => {
|
||||
if (grouped[row.notification_type]) {
|
||||
grouped[row.notification_type].recipients.push(row);
|
||||
}
|
||||
});
|
||||
|
||||
return grouped;
|
||||
},
|
||||
|
||||
// 수신자 추가
|
||||
async add(notificationType, userId, createdBy = null) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO notification_recipients (notification_type, user_id, created_by)
|
||||
VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE is_active = 1`,
|
||||
[notificationType, userId, createdBy]
|
||||
);
|
||||
return result.insertId || result.affectedRows > 0;
|
||||
},
|
||||
|
||||
// 수신자 제거 (soft delete)
|
||||
async remove(notificationType, userId) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE notification_recipients SET is_active = 0
|
||||
WHERE notification_type = ? AND user_id = ?`,
|
||||
[notificationType, userId]
|
||||
);
|
||||
return result.affectedRows > 0;
|
||||
},
|
||||
|
||||
// 수신자 완전 삭제
|
||||
async delete(notificationType, userId) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM notification_recipients
|
||||
WHERE notification_type = ? AND user_id = ?`,
|
||||
[notificationType, userId]
|
||||
);
|
||||
return result.affectedRows > 0;
|
||||
},
|
||||
|
||||
// 유형별 수신자 user_id 목록만 가져오기 (알림 생성용)
|
||||
async getRecipientIds(notificationType) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT user_id FROM notification_recipients
|
||||
WHERE notification_type = ? AND is_active = 1`,
|
||||
[notificationType]
|
||||
);
|
||||
return rows.map(r => r.user_id);
|
||||
},
|
||||
|
||||
// 사용자가 특정 유형의 수신자인지 확인
|
||||
async isRecipient(notificationType, userId) {
|
||||
const db = await getDb();
|
||||
const [[row]] = await db.query(
|
||||
`SELECT 1 FROM notification_recipients
|
||||
WHERE notification_type = ? AND user_id = ? AND is_active = 1`,
|
||||
[notificationType, userId]
|
||||
);
|
||||
return !!row;
|
||||
},
|
||||
|
||||
// 유형별 수신자 일괄 설정
|
||||
async setRecipients(notificationType, userIds, createdBy = null) {
|
||||
const db = await getDb();
|
||||
|
||||
// 기존 수신자 비활성화
|
||||
await db.query(
|
||||
`UPDATE notification_recipients SET is_active = 0
|
||||
WHERE notification_type = ?`,
|
||||
[notificationType]
|
||||
);
|
||||
|
||||
// 새 수신자 추가
|
||||
if (userIds && userIds.length > 0) {
|
||||
const values = userIds.map(userId => [notificationType, userId, createdBy]);
|
||||
await db.query(
|
||||
`INSERT INTO notification_recipients (notification_type, user_id, created_by)
|
||||
VALUES ?
|
||||
ON DUPLICATE KEY UPDATE is_active = 1`,
|
||||
[values]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = notificationRecipientModel;
|
||||
@@ -1,341 +0,0 @@
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
// ==================== 출입 신청 관리 ====================
|
||||
|
||||
const createVisitRequest = async (requestData) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
requester_id,
|
||||
visitor_company,
|
||||
visitor_count = 1,
|
||||
category_id,
|
||||
workplace_id,
|
||||
visit_date,
|
||||
visit_time,
|
||||
purpose_id,
|
||||
notes = null
|
||||
} = requestData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workplace_visit_requests
|
||||
(requester_id, visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[requester_id, visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes]
|
||||
);
|
||||
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
const getAllVisitRequests = async (filters = {}) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count,
|
||||
vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time,
|
||||
vr.purpose_id, vr.notes, vr.status,
|
||||
vr.approved_by, vr.approved_at, vr.rejection_reason,
|
||||
vr.created_at, vr.updated_at,
|
||||
u.username as requester_name, u.name as requester_full_name,
|
||||
wc.category_name, w.workplace_name,
|
||||
vpt.purpose_name,
|
||||
approver.username as approver_name
|
||||
FROM workplace_visit_requests vr
|
||||
INNER JOIN users u ON vr.requester_id = u.user_id
|
||||
INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id
|
||||
INNER JOIN workplaces w ON vr.workplace_id = w.workplace_id
|
||||
INNER JOIN visit_purpose_types vpt ON vr.purpose_id = vpt.purpose_id
|
||||
LEFT JOIN users approver ON vr.approved_by = approver.user_id
|
||||
WHERE 1=1
|
||||
`;
|
||||
|
||||
const params = [];
|
||||
|
||||
if (filters.status) {
|
||||
query += ` AND vr.status = ?`;
|
||||
params.push(filters.status);
|
||||
}
|
||||
if (filters.visit_date) {
|
||||
query += ` AND vr.visit_date = ?`;
|
||||
params.push(filters.visit_date);
|
||||
}
|
||||
if (filters.start_date && filters.end_date) {
|
||||
query += ` AND vr.visit_date BETWEEN ? AND ?`;
|
||||
params.push(filters.start_date, filters.end_date);
|
||||
}
|
||||
if (filters.requester_id) {
|
||||
query += ` AND vr.requester_id = ?`;
|
||||
params.push(filters.requester_id);
|
||||
}
|
||||
if (filters.category_id) {
|
||||
query += ` AND vr.category_id = ?`;
|
||||
params.push(filters.category_id);
|
||||
}
|
||||
|
||||
query += ` ORDER BY vr.visit_date DESC, vr.visit_time DESC, vr.created_at DESC`;
|
||||
|
||||
const [rows] = await db.query(query, params);
|
||||
return rows;
|
||||
};
|
||||
|
||||
const getVisitRequestById = async (requestId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count,
|
||||
vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time,
|
||||
vr.purpose_id, vr.notes, vr.status,
|
||||
vr.approved_by, vr.approved_at, vr.rejection_reason,
|
||||
vr.created_at, vr.updated_at,
|
||||
u.username as requester_name, u.name as requester_full_name,
|
||||
wc.category_name, w.workplace_name,
|
||||
vpt.purpose_name,
|
||||
approver.username as approver_name
|
||||
FROM workplace_visit_requests vr
|
||||
INNER JOIN users u ON vr.requester_id = u.user_id
|
||||
INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id
|
||||
INNER JOIN workplaces w ON vr.workplace_id = w.workplace_id
|
||||
INNER JOIN visit_purpose_types vpt ON vr.purpose_id = vpt.purpose_id
|
||||
LEFT JOIN users approver ON vr.approved_by = approver.user_id
|
||||
WHERE vr.request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
const updateVisitRequest = async (requestId, requestData) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes
|
||||
} = requestData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET visitor_company = ?, visitor_count = ?, category_id = ?, workplace_id = ?,
|
||||
visit_date = ?, visit_time = ?, purpose_id = ?, notes = ?, updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes, requestId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
const deleteVisitRequest = async (requestId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_visit_requests WHERE request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
const approveVisitRequest = async (requestId, approvedBy) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET status = 'approved', approved_by = ?, approved_at = NOW(), updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[approvedBy, requestId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
const rejectVisitRequest = async (requestId, rejectionData) => {
|
||||
const db = await getDb();
|
||||
const { approved_by, rejection_reason } = rejectionData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET status = 'rejected', approved_by = ?, approved_at = NOW(),
|
||||
rejection_reason = ?, updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[approved_by, rejection_reason, requestId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
const updateVisitRequestStatus = async (requestId, status) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET status = ?, updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[status, requestId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 방문 목적 관리 ====================
|
||||
|
||||
const getAllVisitPurposes = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT purpose_id, purpose_name, display_order, is_active, created_at
|
||||
FROM visit_purpose_types
|
||||
ORDER BY display_order ASC, purpose_id ASC`
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
const getActiveVisitPurposes = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT purpose_id, purpose_name, display_order, is_active, created_at
|
||||
FROM visit_purpose_types
|
||||
WHERE is_active = TRUE
|
||||
ORDER BY display_order ASC, purpose_id ASC`
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
const createVisitPurpose = async (purposeData) => {
|
||||
const db = await getDb();
|
||||
const { purpose_name, display_order = 0, is_active = true } = purposeData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO visit_purpose_types (purpose_name, display_order, is_active)
|
||||
VALUES (?, ?, ?)`,
|
||||
[purpose_name, display_order, is_active]
|
||||
);
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
const updateVisitPurpose = async (purposeId, purposeData) => {
|
||||
const db = await getDb();
|
||||
const { purpose_name, display_order, is_active } = purposeData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE visit_purpose_types
|
||||
SET purpose_name = ?, display_order = ?, is_active = ?
|
||||
WHERE purpose_id = ?`,
|
||||
[purpose_name, display_order, is_active, purposeId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
const deleteVisitPurpose = async (purposeId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM visit_purpose_types WHERE purpose_id = ?`,
|
||||
[purposeId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 안전교육 기록 관리 ====================
|
||||
|
||||
const createTrainingRecord = async (trainingData) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
request_id, trainer_id, training_date,
|
||||
training_start_time, training_end_time = null, training_topics = null
|
||||
} = trainingData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO safety_training_records
|
||||
(request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics]
|
||||
);
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
const getTrainingRecordByRequestId = async (requestId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
str.training_id, str.request_id, str.trainer_id, str.training_date,
|
||||
str.training_start_time, str.training_end_time, str.training_topics,
|
||||
str.signature_data, str.completed_at, str.created_at, str.updated_at,
|
||||
u.username as trainer_name, u.name as trainer_full_name
|
||||
FROM safety_training_records str
|
||||
INNER JOIN users u ON str.trainer_id = u.user_id
|
||||
WHERE str.request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
const updateTrainingRecord = async (trainingId, trainingData) => {
|
||||
const db = await getDb();
|
||||
const { training_date, training_start_time, training_end_time, training_topics } = trainingData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE safety_training_records
|
||||
SET training_date = ?, training_start_time = ?, training_end_time = ?,
|
||||
training_topics = ?, updated_at = NOW()
|
||||
WHERE training_id = ?`,
|
||||
[training_date, training_start_time, training_end_time, training_topics, trainingId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
const completeTraining = async (trainingId, signatureData) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE safety_training_records
|
||||
SET signature_data = ?, completed_at = NOW(), updated_at = NOW()
|
||||
WHERE training_id = ?`,
|
||||
[signatureData, trainingId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
const getTrainingRecords = async (filters = {}) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
str.training_id, str.request_id, str.trainer_id, str.training_date,
|
||||
str.training_start_time, str.training_end_time, str.training_topics,
|
||||
str.completed_at, str.created_at, str.updated_at,
|
||||
u.username as trainer_name, u.name as trainer_full_name,
|
||||
vr.visitor_company, vr.visitor_count, vr.visit_date
|
||||
FROM safety_training_records str
|
||||
INNER JOIN users u ON str.trainer_id = u.user_id
|
||||
INNER JOIN workplace_visit_requests vr ON str.request_id = vr.request_id
|
||||
WHERE 1=1
|
||||
`;
|
||||
|
||||
const params = [];
|
||||
|
||||
if (filters.training_date) {
|
||||
query += ` AND str.training_date = ?`;
|
||||
params.push(filters.training_date);
|
||||
}
|
||||
if (filters.start_date && filters.end_date) {
|
||||
query += ` AND str.training_date BETWEEN ? AND ?`;
|
||||
params.push(filters.start_date, filters.end_date);
|
||||
}
|
||||
if (filters.trainer_id) {
|
||||
query += ` AND str.trainer_id = ?`;
|
||||
params.push(filters.trainer_id);
|
||||
}
|
||||
|
||||
query += ` ORDER BY str.training_date DESC, str.training_start_time DESC`;
|
||||
|
||||
const [rows] = await db.query(query, params);
|
||||
return rows;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createVisitRequest,
|
||||
getAllVisitRequests,
|
||||
getVisitRequestById,
|
||||
updateVisitRequest,
|
||||
deleteVisitRequest,
|
||||
approveVisitRequest,
|
||||
rejectVisitRequest,
|
||||
updateVisitRequestStatus,
|
||||
getAllVisitPurposes,
|
||||
getActiveVisitPurposes,
|
||||
createVisitPurpose,
|
||||
updateVisitPurpose,
|
||||
deleteVisitPurpose,
|
||||
createTrainingRecord,
|
||||
getTrainingRecordByRequestId,
|
||||
updateTrainingRecord,
|
||||
completeTraining,
|
||||
getTrainingRecords
|
||||
};
|
||||
Reference in New Issue
Block a user