Files
TK-FB-Project/api.hyungi.net/models/visitRequestModel.js
Hyungi Ahn b6485e3140 feat: 대시보드 작업장 현황 지도 구현
- 실시간 작업장 현황을 지도로 시각화
- 작업장 관리 페이지에서 정의한 구역 정보 활용
- TBM 작업자 및 방문자 현황 표시

주요 변경사항:
- dashboard.html: 작업장 현황 섹션 추가 (기존 작업 현황 테이블 제거)
- workplace-status.js: 지도 렌더링 및 데이터 통합 로직 구현
- modern-dashboard.js: 삭제된 DOM 요소 조건부 체크 추가

시각화 방식:
- 인원 없음: 회색 테두리 + 작업장 이름
- 내부 작업자: 파란색 영역 + 인원 수
- 외부 방문자: 보라색 영역 + 인원 수
- 둘 다: 초록색 영역 + 총 인원 수

기술 구현:
- Canvas API 기반 사각형 영역 렌더링
- map-regions API를 통한 데이터 일관성 보장
- 클릭 이벤트로 상세 정보 모달 표시

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-29 15:46:47 +09:00

506 lines
13 KiB
JavaScript

const { getDb } = require('../dbPool');
// ==================== 출입 신청 관리 ====================
/**
* 출입 신청 생성
*/
const createVisitRequest = async (requestData, callback) => {
try {
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]
);
callback(null, result.insertId);
} catch (err) {
callback(err);
}
};
/**
* 출입 신청 목록 조회 (필터 옵션 포함)
*/
const getAllVisitRequests = async (filters = {}, callback) => {
try {
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);
callback(null, rows);
} catch (err) {
callback(err);
}
};
/**
* 출입 신청 상세 조회
*/
const getVisitRequestById = async (requestId, callback) => {
try {
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]
);
callback(null, rows[0]);
} catch (err) {
callback(err);
}
};
/**
* 출입 신청 수정
*/
const updateVisitRequest = async (requestId, requestData, callback) => {
try {
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]
);
callback(null, result);
} catch (err) {
callback(err);
}
};
/**
* 출입 신청 삭제
*/
const deleteVisitRequest = async (requestId, callback) => {
try {
const db = await getDb();
const [result] = await db.query(
`DELETE FROM workplace_visit_requests WHERE request_id = ?`,
[requestId]
);
callback(null, result);
} catch (err) {
callback(err);
}
};
/**
* 출입 신청 승인
*/
const approveVisitRequest = async (requestId, approvedBy, callback) => {
try {
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]
);
callback(null, result);
} catch (err) {
callback(err);
}
};
/**
* 출입 신청 반려
*/
const rejectVisitRequest = async (requestId, rejectionData, callback) => {
try {
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]
);
callback(null, result);
} catch (err) {
callback(err);
}
};
/**
* 출입 신청 상태 변경
*/
const updateVisitRequestStatus = async (requestId, status, callback) => {
try {
const db = await getDb();
const [result] = await db.query(
`UPDATE workplace_visit_requests
SET status = ?, updated_at = NOW()
WHERE request_id = ?`,
[status, requestId]
);
callback(null, result);
} catch (err) {
callback(err);
}
};
// ==================== 방문 목적 관리 ====================
/**
* 모든 방문 목적 조회
*/
const getAllVisitPurposes = async (callback) => {
try {
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`
);
callback(null, rows);
} catch (err) {
callback(err);
}
};
/**
* 활성 방문 목적만 조회
*/
const getActiveVisitPurposes = async (callback) => {
try {
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`
);
callback(null, rows);
} catch (err) {
callback(err);
}
};
/**
* 방문 목적 추가
*/
const createVisitPurpose = async (purposeData, callback) => {
try {
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]
);
callback(null, result.insertId);
} catch (err) {
callback(err);
}
};
/**
* 방문 목적 수정
*/
const updateVisitPurpose = async (purposeId, purposeData, callback) => {
try {
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]
);
callback(null, result);
} catch (err) {
callback(err);
}
};
/**
* 방문 목적 삭제
*/
const deleteVisitPurpose = async (purposeId, callback) => {
try {
const db = await getDb();
const [result] = await db.query(
`DELETE FROM visit_purpose_types WHERE purpose_id = ?`,
[purposeId]
);
callback(null, result);
} catch (err) {
callback(err);
}
};
// ==================== 안전교육 기록 관리 ====================
/**
* 안전교육 기록 생성
*/
const createTrainingRecord = async (trainingData, callback) => {
try {
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]
);
callback(null, result.insertId);
} catch (err) {
callback(err);
}
};
/**
* 특정 출입 신청의 안전교육 기록 조회
*/
const getTrainingRecordByRequestId = async (requestId, callback) => {
try {
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]
);
callback(null, rows[0]);
} catch (err) {
callback(err);
}
};
/**
* 안전교육 기록 수정
*/
const updateTrainingRecord = async (trainingId, trainingData, callback) => {
try {
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]
);
callback(null, result);
} catch (err) {
callback(err);
}
};
/**
* 안전교육 완료 (서명 포함)
*/
const completeTraining = async (trainingId, signatureData, callback) => {
try {
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]
);
callback(null, result);
} catch (err) {
callback(err);
}
};
/**
* 안전교육 목록 조회 (날짜별 필터)
*/
const getTrainingRecords = async (filters = {}, callback) => {
try {
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);
callback(null, rows);
} catch (err) {
callback(err);
}
};
module.exports = {
// 출입 신청
createVisitRequest,
getAllVisitRequests,
getVisitRequestById,
updateVisitRequest,
deleteVisitRequest,
approveVisitRequest,
rejectVisitRequest,
updateVisitRequestStatus,
// 방문 목적
getAllVisitPurposes,
getActiveVisitPurposes,
createVisitPurpose,
updateVisitPurpose,
deleteVisitPurpose,
// 안전교육
createTrainingRecord,
getTrainingRecordByRequestId,
updateTrainingRecord,
completeTraining,
getTrainingRecords
};