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>
367 lines
11 KiB
JavaScript
367 lines
11 KiB
JavaScript
const { getPool } = require('../middleware/auth');
|
|
|
|
// ==================== 출입 신청 관리 ====================
|
|
|
|
const createVisitRequest = async (requestData) => {
|
|
const db = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
const [result] = await db.query(
|
|
`DELETE FROM workplace_visit_requests WHERE request_id = ?`,
|
|
[requestId]
|
|
);
|
|
return result;
|
|
};
|
|
|
|
const approveVisitRequest = async (requestId, approvedBy) => {
|
|
const db = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
const [result] = await db.query(
|
|
`DELETE FROM visit_purpose_types WHERE purpose_id = ?`,
|
|
[purposeId]
|
|
);
|
|
return result;
|
|
};
|
|
|
|
// ==================== 안전교육 기록 관리 ====================
|
|
|
|
const createTrainingRecord = async (trainingData) => {
|
|
const db = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
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 = getPool();
|
|
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;
|
|
};
|
|
|
|
// ==================== 작업장 분류/작업장 조회 ====================
|
|
|
|
const getAllCategories = async () => {
|
|
const db = getPool();
|
|
const [rows] = await db.query(
|
|
'SELECT category_id, category_name FROM workplace_categories ORDER BY category_name'
|
|
);
|
|
return rows;
|
|
};
|
|
|
|
const getWorkplacesByCategory = async (categoryId) => {
|
|
const db = getPool();
|
|
let query = 'SELECT workplace_id, workplace_name, category_id FROM workplaces';
|
|
const params = [];
|
|
if (categoryId) {
|
|
query += ' WHERE category_id = ?';
|
|
params.push(categoryId);
|
|
}
|
|
query += ' ORDER BY workplace_name';
|
|
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,
|
|
getAllCategories,
|
|
getWorkplacesByCategory
|
|
};
|