feat: 다수 기능 개선 - 순찰, 출근, 작업분석, 모바일 UI 등
- 순찰/점검 기능 개선 (zone-detail 페이지 추가) - 출근/근태 시스템 개선 (연차 조회, 근무현황) - 작업분석 대분류 그룹화 및 마이그레이션 스크립트 - 모바일 네비게이션 UI 추가 - NAS 배포 도구 및 문서 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -289,6 +289,507 @@ const PatrolController = {
|
||||
console.error('작업장별 점검 현황 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 작업장 상세 정보 (통합) ====================
|
||||
|
||||
// 작업장 상세 정보 조회 (시설물, 안전신고, 부적합, 출입, TBM)
|
||||
getWorkplaceDetail: async (req, res) => {
|
||||
try {
|
||||
const { workplaceId } = req.params;
|
||||
const { date } = req.query; // 기본: 오늘
|
||||
const targetDate = date || new Date().toISOString().slice(0, 10);
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
// 1. 작업장 기본 정보 (카테고리 지도 이미지 포함)
|
||||
const [workplaceInfo] = await db.query(`
|
||||
SELECT w.*, wc.category_name, wc.layout_image as category_layout_image
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE w.workplace_id = ?
|
||||
`, [workplaceId]);
|
||||
|
||||
if (!workplaceInfo.length) {
|
||||
return res.status(404).json({ success: false, message: '작업장을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 2. 설비 현황 (해당 작업장 - 원래 위치 또는 현재 위치)
|
||||
let equipments = [];
|
||||
try {
|
||||
const [eqResult] = await db.query(`
|
||||
SELECT e.equipment_id, e.equipment_name, e.equipment_code, e.equipment_type,
|
||||
e.status, e.notes, e.workplace_id,
|
||||
e.map_x_percent, e.map_y_percent, e.map_width_percent, e.map_height_percent,
|
||||
e.is_temporarily_moved, e.current_workplace_id,
|
||||
e.current_map_x_percent, e.current_map_y_percent,
|
||||
e.current_map_width_percent, e.current_map_height_percent,
|
||||
e.moved_at,
|
||||
ow.workplace_name as original_workplace_name,
|
||||
cw.workplace_name as current_workplace_name,
|
||||
CASE
|
||||
WHEN e.status IN ('maintenance', 'repair_needed', 'repair_external') THEN 1
|
||||
WHEN e.is_temporarily_moved = 1 THEN 1
|
||||
ELSE 0
|
||||
END as needs_attention
|
||||
FROM equipments e
|
||||
LEFT JOIN workplaces ow ON e.workplace_id = ow.workplace_id
|
||||
LEFT JOIN workplaces cw ON e.current_workplace_id = cw.workplace_id
|
||||
WHERE (e.workplace_id = ? OR e.current_workplace_id = ?)
|
||||
AND e.status != 'inactive'
|
||||
ORDER BY needs_attention DESC, e.equipment_name
|
||||
`, [workplaceId, workplaceId]);
|
||||
equipments = eqResult;
|
||||
} catch (eqError) {
|
||||
console.log('설비 조회 스킵 (테이블 없음 또는 오류):', eqError.message);
|
||||
}
|
||||
|
||||
// 3. 수리 요청 현황 (미완료) - 테이블 존재 여부 확인 후 조회
|
||||
let repairRequests = [];
|
||||
try {
|
||||
const [repairResult] = await db.query(`
|
||||
SELECT er.request_id, er.request_date, er.repair_category, er.description,
|
||||
er.priority, er.status, e.equipment_name, e.equipment_code
|
||||
FROM equipment_repair_requests er
|
||||
JOIN equipments e ON er.equipment_id = e.equipment_id
|
||||
WHERE e.workplace_id = ? AND er.status NOT IN ('completed', 'cancelled')
|
||||
ORDER BY
|
||||
CASE er.priority WHEN 'emergency' THEN 1 WHEN 'high' THEN 2 WHEN 'normal' THEN 3 ELSE 4 END,
|
||||
er.request_date DESC
|
||||
LIMIT 10
|
||||
`, [workplaceId]);
|
||||
repairRequests = repairResult;
|
||||
} catch (repairError) {
|
||||
console.log('수리요청 조회 스킵 (테이블 없음 또는 오류):', repairError.message);
|
||||
}
|
||||
|
||||
// 4. 안전 신고 및 부적합 사항 - 테이블 존재 여부 확인 후 조회
|
||||
let workIssues = [];
|
||||
try {
|
||||
const [issueResult] = await db.query(`
|
||||
SELECT wi.report_id, wi.issue_type, wi.title, wi.description,
|
||||
wi.status, wi.severity, wi.created_at, wi.resolved_at,
|
||||
wic.category_name, wic.issue_type as category_type,
|
||||
u.name as reporter_name
|
||||
FROM work_issue_reports wi
|
||||
LEFT JOIN issue_report_categories wic ON wi.category_id = wic.category_id
|
||||
LEFT JOIN users u ON wi.reporter_id = u.user_id
|
||||
WHERE wi.workplace_id = ?
|
||||
AND wi.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
|
||||
ORDER BY wi.created_at DESC
|
||||
LIMIT 20
|
||||
`, [workplaceId]);
|
||||
workIssues = issueResult;
|
||||
} catch (issueError) {
|
||||
console.log('신고 조회 스킵 (테이블 없음 또는 오류):', issueError.message);
|
||||
}
|
||||
|
||||
// 5. 오늘의 출입 기록 (해당 공장 카테고리)
|
||||
const categoryId = workplaceInfo[0].category_id;
|
||||
let visitRecords = [];
|
||||
try {
|
||||
const [visitResult] = await db.query(`
|
||||
SELECT vr.request_id, vr.visitor_name, vr.visitor_company, vr.visit_purpose,
|
||||
vr.visit_date, vr.visit_time_from, vr.visit_time_to, vr.status,
|
||||
vr.vehicle_number, vr.companion_count,
|
||||
vp.purpose_name, u.name as requester_name
|
||||
FROM workplace_visit_requests vr
|
||||
LEFT JOIN visit_purpose_types vp ON vr.purpose_id = vp.purpose_id
|
||||
LEFT JOIN users u ON vr.requester_id = u.user_id
|
||||
WHERE vr.category_id = ? AND vr.visit_date = ? AND vr.status = 'approved'
|
||||
ORDER BY vr.visit_time_from
|
||||
`, [categoryId, targetDate]);
|
||||
visitRecords = visitResult;
|
||||
} catch (visitError) {
|
||||
console.log('출입기록 조회 스킵 (테이블 없음 또는 오류):', visitError.message);
|
||||
}
|
||||
|
||||
// 6. 오늘의 TBM 세션 (해당 공장 카테고리)
|
||||
let tbmSessions = [];
|
||||
try {
|
||||
const [tbmResult] = await db.query(`
|
||||
SELECT ts.session_id, ts.session_date, ts.work_location, ts.status,
|
||||
ts.work_content, ts.safety_measures, ts.team_size,
|
||||
t.task_name, wt.name as work_type_name,
|
||||
u.name as leader_name, w.worker_name as leader_worker_name
|
||||
FROM tbm_sessions ts
|
||||
LEFT JOIN tasks t ON ts.task_id = t.task_id
|
||||
LEFT JOIN work_types wt ON t.work_type_id = wt.id
|
||||
LEFT JOIN users u ON ts.leader_id = u.user_id
|
||||
LEFT JOIN workers w ON ts.leader_worker_id = w.worker_id
|
||||
WHERE ts.category_id = ? AND ts.session_date = ?
|
||||
ORDER BY ts.created_at DESC
|
||||
`, [categoryId, targetDate]);
|
||||
tbmSessions = tbmResult;
|
||||
} catch (tbmError) {
|
||||
console.log('TBM 조회 스킵 (테이블 없음 또는 오류):', tbmError.message);
|
||||
}
|
||||
|
||||
// 7. TBM 팀원 정보 (세션별)
|
||||
let tbmWithTeams = [];
|
||||
try {
|
||||
tbmWithTeams = await Promise.all(tbmSessions.map(async (session) => {
|
||||
const [team] = await db.query(`
|
||||
SELECT tta.assignment_id, w.worker_name, w.occupation,
|
||||
tta.attendance_status, tta.signature_image
|
||||
FROM tbm_team_assignments tta
|
||||
JOIN workers w ON tta.worker_id = w.worker_id
|
||||
WHERE tta.session_id = ?
|
||||
ORDER BY w.worker_name
|
||||
`, [session.session_id]);
|
||||
return { ...session, team };
|
||||
}));
|
||||
} catch (teamError) {
|
||||
console.log('TBM 팀원 조회 스킵:', teamError.message);
|
||||
tbmWithTeams = tbmSessions.map(s => ({ ...s, team: [] }));
|
||||
}
|
||||
|
||||
// 8. 최근 순회점검 결과 (해당 작업장)
|
||||
let recentPatrol = [];
|
||||
try {
|
||||
const [patrolResult] = await db.query(`
|
||||
SELECT ps.session_id, ps.patrol_date, ps.patrol_time, ps.status,
|
||||
ps.notes, u.name as inspector_name,
|
||||
(SELECT COUNT(*) FROM patrol_check_records pcr
|
||||
WHERE pcr.session_id = ps.session_id AND pcr.workplace_id = ?) as checked_count,
|
||||
(SELECT COUNT(*) FROM patrol_check_records pcr
|
||||
WHERE pcr.session_id = ps.session_id AND pcr.workplace_id = ?
|
||||
AND pcr.check_result IN ('warning', 'bad')) as issue_count
|
||||
FROM daily_patrol_sessions ps
|
||||
LEFT JOIN users u ON ps.inspector_id = u.user_id
|
||||
WHERE ps.category_id = ? AND ps.patrol_date >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||
ORDER BY ps.patrol_date DESC, ps.patrol_time DESC
|
||||
LIMIT 5
|
||||
`, [workplaceId, workplaceId, categoryId]);
|
||||
recentPatrol = patrolResult;
|
||||
} catch (patrolError) {
|
||||
console.log('순회점검 조회 스킵 (테이블 없음 또는 오류):', patrolError.message);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
workplace: workplaceInfo[0],
|
||||
equipments: equipments,
|
||||
repairRequests: repairRequests,
|
||||
workIssues: {
|
||||
safety: workIssues.filter(i => i.category_type === 'safety'),
|
||||
nonconformity: workIssues.filter(i => i.category_type === 'nonconformity'),
|
||||
all: workIssues
|
||||
},
|
||||
visitRecords: visitRecords,
|
||||
tbmSessions: tbmWithTeams,
|
||||
recentPatrol: recentPatrol,
|
||||
summary: {
|
||||
equipmentCount: equipments.length,
|
||||
needsAttention: equipments.filter(e => e.needs_attention).length,
|
||||
pendingRepairs: repairRequests.length,
|
||||
openIssues: workIssues.filter(i => i.status !== 'closed').length,
|
||||
todayVisitors: visitRecords.reduce((sum, v) => sum + 1 + (v.companion_count || 0), 0),
|
||||
todayTbmSessions: tbmSessions.length
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('작업장 상세 정보 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 구역 내 등록 물품/시설물 ====================
|
||||
|
||||
// 구역 내 물품/시설물 목록 조회
|
||||
getZoneItems: async (req, res) => {
|
||||
try {
|
||||
const { workplaceId } = req.params;
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
// 테이블이 없으면 생성
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS workplace_zone_items (
|
||||
item_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
workplace_id INT NOT NULL,
|
||||
item_name VARCHAR(200) NOT NULL COMMENT '물품/시설물 명칭',
|
||||
item_type VARCHAR(50) DEFAULT 'general' COMMENT '유형 (heavy_equipment, hazardous, storage, general 등)',
|
||||
description TEXT COMMENT '상세 설명',
|
||||
x_percent DECIMAL(5,2) NOT NULL COMMENT '영역 시작 X 좌표 (%)',
|
||||
y_percent DECIMAL(5,2) NOT NULL COMMENT '영역 시작 Y 좌표 (%)',
|
||||
width_percent DECIMAL(5,2) DEFAULT 10 COMMENT '영역 너비 (%)',
|
||||
height_percent DECIMAL(5,2) DEFAULT 10 COMMENT '영역 높이 (%)',
|
||||
color VARCHAR(20) DEFAULT '#3b82f6' COMMENT '표시 색상',
|
||||
warning_level VARCHAR(20) DEFAULT 'normal' COMMENT '주의 수준 (normal, caution, danger)',
|
||||
quantity INT DEFAULT 1 COMMENT '수량',
|
||||
unit VARCHAR(20) DEFAULT '개' COMMENT '단위',
|
||||
weight_kg DECIMAL(10,2) DEFAULT NULL COMMENT '중량 (kg)',
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_by INT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_workplace (workplace_id),
|
||||
INDEX idx_type (item_type)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='구역 내 등록 물품/시설물'
|
||||
`);
|
||||
|
||||
// 새 컬럼 추가 (없으면)
|
||||
try {
|
||||
await db.query(`ALTER TABLE workplace_zone_items ADD COLUMN project_type VARCHAR(20) DEFAULT 'non_project'`);
|
||||
} catch (e) { /* 이미 존재 */ }
|
||||
try {
|
||||
await db.query(`ALTER TABLE workplace_zone_items ADD COLUMN project_id INT NULL`);
|
||||
} catch (e) { /* 이미 존재 */ }
|
||||
|
||||
const [items] = await db.query(`
|
||||
SELECT zi.*, p.project_name
|
||||
FROM workplace_zone_items zi
|
||||
LEFT JOIN projects p ON zi.project_id = p.project_id
|
||||
WHERE zi.workplace_id = ? AND zi.is_active = TRUE
|
||||
ORDER BY zi.warning_level DESC, zi.item_name
|
||||
`, [workplaceId]);
|
||||
|
||||
// 사진 테이블 존재 확인 및 사진 조회
|
||||
try {
|
||||
for (const item of items) {
|
||||
const [photos] = await db.query(`
|
||||
SELECT photo_id, photo_url, created_at
|
||||
FROM zone_item_photos
|
||||
WHERE item_id = ?
|
||||
ORDER BY created_at DESC
|
||||
`, [item.item_id]);
|
||||
item.photos = photos || [];
|
||||
}
|
||||
} catch (e) {
|
||||
// 사진 테이블이 없으면 무시
|
||||
items.forEach(item => item.photos = []);
|
||||
}
|
||||
|
||||
res.json({ success: true, data: items });
|
||||
} catch (error) {
|
||||
console.error('구역 물품 목록 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
},
|
||||
|
||||
// 구역 현황 등록
|
||||
createZoneItem: async (req, res) => {
|
||||
try {
|
||||
const { workplaceId } = req.params;
|
||||
const { item_name, item_type, description, x_percent, y_percent, width_percent, height_percent,
|
||||
color, warning_level, project_type, project_id } = req.body;
|
||||
const createdBy = req.user?.user_id;
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
if (!item_name || x_percent === undefined || y_percent === undefined) {
|
||||
return res.status(400).json({ success: false, message: '필수 정보가 누락되었습니다.' });
|
||||
}
|
||||
|
||||
// 테이블에 새 컬럼 추가 (없으면)
|
||||
try {
|
||||
await db.query(`ALTER TABLE workplace_zone_items ADD COLUMN project_type VARCHAR(20) DEFAULT 'non_project'`);
|
||||
} catch (e) { /* 이미 존재 */ }
|
||||
try {
|
||||
await db.query(`ALTER TABLE workplace_zone_items ADD COLUMN project_id INT NULL`);
|
||||
} catch (e) { /* 이미 존재 */ }
|
||||
|
||||
const [result] = await db.query(`
|
||||
INSERT INTO workplace_zone_items
|
||||
(workplace_id, item_name, item_type, description, x_percent, y_percent, width_percent, height_percent,
|
||||
color, warning_level, project_type, project_id, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, [workplaceId, item_name, item_type || 'working', description, x_percent, y_percent,
|
||||
width_percent || 5, height_percent || 5, color || '#3b82f6', warning_level || 'good',
|
||||
project_type || 'non_project', project_id || null, createdBy]);
|
||||
|
||||
const newItemId = result.insertId;
|
||||
|
||||
// 등록 이력 저장
|
||||
try {
|
||||
await db.query(`
|
||||
INSERT INTO zone_item_history (item_id, action_type, new_values, changed_by)
|
||||
VALUES (?, 'created', ?, ?)
|
||||
`, [newItemId, JSON.stringify({ item_name, item_type, warning_level, project_type }), createdBy]);
|
||||
} catch (e) { /* 테이블 없으면 무시 */ }
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { item_id: newItemId },
|
||||
message: '현황이 등록되었습니다.'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('구역 현황 등록 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
},
|
||||
|
||||
// 구역 현황 수정
|
||||
updateZoneItem: async (req, res) => {
|
||||
try {
|
||||
const { itemId } = req.params;
|
||||
const { item_name, item_type, description, x_percent, y_percent, width_percent, height_percent,
|
||||
color, warning_level, project_type, project_id } = req.body;
|
||||
const userId = req.user?.user_id;
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
// 이력 테이블 생성 (없으면)
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS zone_item_history (
|
||||
history_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
item_id INT NOT NULL,
|
||||
action_type VARCHAR(20) NOT NULL COMMENT 'created, updated, deleted',
|
||||
changed_fields TEXT COMMENT '변경된 필드 JSON',
|
||||
old_values TEXT COMMENT '이전 값 JSON',
|
||||
new_values TEXT COMMENT '새 값 JSON',
|
||||
changed_by INT,
|
||||
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_item (item_id),
|
||||
INDEX idx_date (changed_at)
|
||||
)
|
||||
`);
|
||||
|
||||
// 기존 데이터 조회 (이력용)
|
||||
const [oldData] = await db.query(`SELECT * FROM workplace_zone_items WHERE item_id = ?`, [itemId]);
|
||||
const oldItem = oldData[0];
|
||||
|
||||
// 업데이트
|
||||
await db.query(`
|
||||
UPDATE workplace_zone_items SET
|
||||
item_name = COALESCE(?, item_name),
|
||||
item_type = COALESCE(?, item_type),
|
||||
description = ?,
|
||||
x_percent = COALESCE(?, x_percent),
|
||||
y_percent = COALESCE(?, y_percent),
|
||||
width_percent = COALESCE(?, width_percent),
|
||||
height_percent = COALESCE(?, height_percent),
|
||||
color = COALESCE(?, color),
|
||||
warning_level = COALESCE(?, warning_level),
|
||||
project_type = COALESCE(?, project_type),
|
||||
project_id = ?
|
||||
WHERE item_id = ?
|
||||
`, [item_name, item_type, description, x_percent, y_percent, width_percent, height_percent,
|
||||
color, warning_level, project_type, project_id, itemId]);
|
||||
|
||||
// 변경 이력 저장
|
||||
if (oldItem) {
|
||||
const changedFields = [];
|
||||
const oldValues = {};
|
||||
const newValues = {};
|
||||
|
||||
const fieldMap = { item_name, item_type, description, warning_level, project_type, project_id };
|
||||
for (const [key, newVal] of Object.entries(fieldMap)) {
|
||||
if (newVal !== undefined && oldItem[key] !== newVal) {
|
||||
changedFields.push(key);
|
||||
oldValues[key] = oldItem[key];
|
||||
newValues[key] = newVal;
|
||||
}
|
||||
}
|
||||
|
||||
if (changedFields.length > 0) {
|
||||
await db.query(`
|
||||
INSERT INTO zone_item_history (item_id, action_type, changed_fields, old_values, new_values, changed_by)
|
||||
VALUES (?, 'updated', ?, ?, ?, ?)
|
||||
`, [itemId, JSON.stringify(changedFields), JSON.stringify(oldValues), JSON.stringify(newValues), userId]);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ success: true, message: '현황이 수정되었습니다.' });
|
||||
} catch (error) {
|
||||
console.error('구역 현황 수정 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
},
|
||||
|
||||
// 구역 현황 사진 업로드
|
||||
uploadZoneItemPhoto: async (req, res) => {
|
||||
try {
|
||||
const { item_id } = req.body;
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ success: false, message: '파일이 없습니다.' });
|
||||
}
|
||||
|
||||
// 사진 테이블 생성 (없으면)
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS zone_item_photos (
|
||||
photo_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
item_id INT NOT NULL,
|
||||
photo_url VARCHAR(500) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_item_id (item_id)
|
||||
)
|
||||
`);
|
||||
|
||||
const photoUrl = `/uploads/${req.file.filename}`;
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO zone_item_photos (item_id, photo_url) VALUES (?, ?)`,
|
||||
[item_id, photoUrl]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { photo_id: result.insertId, photo_url: photoUrl },
|
||||
message: '사진이 업로드되었습니다.'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('사진 업로드 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
},
|
||||
|
||||
// 구역 현황 삭제
|
||||
deleteZoneItem: async (req, res) => {
|
||||
try {
|
||||
const { itemId } = req.params;
|
||||
const userId = req.user?.user_id;
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
// 기존 데이터 조회 (이력용)
|
||||
const [oldData] = await db.query(`SELECT * FROM workplace_zone_items WHERE item_id = ?`, [itemId]);
|
||||
const oldItem = oldData[0];
|
||||
|
||||
// 소프트 삭제
|
||||
await db.query(`UPDATE workplace_zone_items SET is_active = FALSE WHERE item_id = ?`, [itemId]);
|
||||
|
||||
// 삭제 이력 저장
|
||||
if (oldItem) {
|
||||
await db.query(`
|
||||
INSERT INTO zone_item_history (item_id, action_type, old_values, changed_by)
|
||||
VALUES (?, 'deleted', ?, ?)
|
||||
`, [itemId, JSON.stringify({ item_name: oldItem.item_name, item_type: oldItem.item_type }), userId]);
|
||||
}
|
||||
|
||||
res.json({ success: true, message: '현황이 삭제되었습니다.' });
|
||||
} catch (error) {
|
||||
console.error('구역 현황 삭제 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
},
|
||||
|
||||
// 구역 현황 이력 조회
|
||||
getZoneItemHistory: async (req, res) => {
|
||||
try {
|
||||
const { itemId } = req.params;
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
const [history] = await db.query(`
|
||||
SELECT h.*, u.full_name as changed_by_name
|
||||
FROM zone_item_history h
|
||||
LEFT JOIN users u ON h.changed_by = u.user_id
|
||||
WHERE h.item_id = ?
|
||||
ORDER BY h.changed_at DESC
|
||||
LIMIT 50
|
||||
`, [itemId]);
|
||||
|
||||
res.json({ success: true, data: history });
|
||||
} catch (error) {
|
||||
console.error('현황 이력 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user