feat: 일일순회점검 시스템 구축 및 관리 기능 개선
- 일일순회점검 시스템 신규 구현 - DB 테이블: patrol_checklist_items, daily_patrol_sessions, patrol_check_records, workplace_items, item_types - API: /api/patrol/* 엔드포인트 - 프론트엔드: 지도 기반 작업장 점검 UI - 설비 관리 기능 개선 - 구매 관련 필드 추가 (구매일, 가격, 공급업체 등) - 설비 코드 자동 생성 (TKP-XXX 형식) - 작업장 관리 개선 - 레이아웃 이미지 업로드 기능 - 마커 위치 저장 기능 - 부서 관리 기능 추가 - 사이드바 네비게이션 카테고리 재구성 - 이미지 401 오류 수정 (정적 파일 경로 처리) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ class AttendanceModel {
|
||||
wat.type_code as attendance_type_code,
|
||||
vt.type_name as vacation_type_name,
|
||||
vt.type_code as vacation_type_code,
|
||||
vt.hours_deduction as vacation_hours
|
||||
vt.deduct_days as vacation_days
|
||||
FROM daily_attendance_records dar
|
||||
LEFT JOIN workers w ON dar.worker_id = w.worker_id
|
||||
LEFT JOIN work_attendance_types wat ON dar.attendance_type_id = wat.id
|
||||
@@ -315,7 +315,8 @@ class AttendanceModel {
|
||||
`, [workerId, date]);
|
||||
|
||||
const currentHours = parseFloat(workHours[0].total_hours);
|
||||
const vacationHours = parseFloat(vacationTypeInfo.hours_deduction);
|
||||
// deduct_days를 시간으로 변환 (1일 = 8시간)
|
||||
const vacationHours = parseFloat(vacationTypeInfo.deduct_days) * 8;
|
||||
const totalHours = currentHours + vacationHours;
|
||||
|
||||
// 근로 유형 결정
|
||||
|
||||
120
api.hyungi.net/models/departmentModel.js
Normal file
120
api.hyungi.net/models/departmentModel.js
Normal file
@@ -0,0 +1,120 @@
|
||||
// models/departmentModel.js
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
const departmentModel = {
|
||||
// 모든 부서 조회 (계층 구조 포함)
|
||||
async getAll() {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT d.*,
|
||||
p.department_name as parent_name,
|
||||
(SELECT COUNT(*) FROM workers w WHERE w.department_id = d.department_id AND w.status = 'active') as worker_count
|
||||
FROM departments d
|
||||
LEFT JOIN departments p ON d.parent_id = p.department_id
|
||||
ORDER BY d.display_order, d.department_name
|
||||
`);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// 활성 부서만 조회
|
||||
async getActive() {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT d.*,
|
||||
p.department_name as parent_name,
|
||||
(SELECT COUNT(*) FROM workers w WHERE w.department_id = d.department_id AND w.status = 'active') as worker_count
|
||||
FROM departments d
|
||||
LEFT JOIN departments p ON d.parent_id = p.department_id
|
||||
WHERE d.is_active = TRUE
|
||||
ORDER BY d.display_order, d.department_name
|
||||
`);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// 부서 ID로 조회
|
||||
async getById(departmentId) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT d.*,
|
||||
p.department_name as parent_name
|
||||
FROM departments d
|
||||
LEFT JOIN departments p ON d.parent_id = p.department_id
|
||||
WHERE d.department_id = ?
|
||||
`, [departmentId]);
|
||||
return rows[0];
|
||||
},
|
||||
|
||||
// 부서 생성
|
||||
async create(data) {
|
||||
const db = await getDb();
|
||||
const { department_name, parent_id, description, is_active, display_order } = data;
|
||||
const [result] = await db.query(`
|
||||
INSERT INTO departments (department_name, parent_id, description, is_active, display_order)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`, [department_name, parent_id || null, description || null, is_active !== false, display_order || 0]);
|
||||
return result.insertId;
|
||||
},
|
||||
|
||||
// 부서 수정
|
||||
async update(departmentId, data) {
|
||||
const db = await getDb();
|
||||
const { department_name, parent_id, description, is_active, display_order } = data;
|
||||
const [result] = await db.query(`
|
||||
UPDATE departments
|
||||
SET department_name = ?, parent_id = ?, description = ?, is_active = ?, display_order = ?
|
||||
WHERE department_id = ?
|
||||
`, [department_name, parent_id || null, description || null, is_active, display_order || 0, departmentId]);
|
||||
return result.affectedRows > 0;
|
||||
},
|
||||
|
||||
// 부서 삭제
|
||||
async delete(departmentId) {
|
||||
const db = await getDb();
|
||||
// 하위 부서가 있는지 확인
|
||||
const [children] = await db.query('SELECT COUNT(*) as count FROM departments WHERE parent_id = ?', [departmentId]);
|
||||
if (children[0].count > 0) {
|
||||
throw new Error('하위 부서가 있어 삭제할 수 없습니다.');
|
||||
}
|
||||
// 소속 작업자가 있는지 확인
|
||||
const [workers] = await db.query('SELECT COUNT(*) as count FROM workers WHERE department_id = ?', [departmentId]);
|
||||
if (workers[0].count > 0) {
|
||||
throw new Error('소속 작업자가 있어 삭제할 수 없습니다. 먼저 작업자를 다른 부서로 이동하세요.');
|
||||
}
|
||||
const [result] = await db.query('DELETE FROM departments WHERE department_id = ?', [departmentId]);
|
||||
return result.affectedRows > 0;
|
||||
},
|
||||
|
||||
// 부서별 작업자 조회
|
||||
async getWorkersByDepartment(departmentId) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT w.*, d.department_name, u.user_id, u.username
|
||||
FROM workers w
|
||||
LEFT JOIN departments d ON w.department_id = d.department_id
|
||||
LEFT JOIN users u ON u.worker_id = w.worker_id
|
||||
WHERE w.department_id = ?
|
||||
ORDER BY w.worker_name
|
||||
`, [departmentId]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// 작업자 부서 변경
|
||||
async moveWorker(workerId, departmentId) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`
|
||||
UPDATE workers SET department_id = ? WHERE worker_id = ?
|
||||
`, [departmentId, workerId]);
|
||||
return result.affectedRows > 0;
|
||||
},
|
||||
|
||||
// 여러 작업자 부서 일괄 변경
|
||||
async moveWorkers(workerIds, departmentId) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`
|
||||
UPDATE workers SET department_id = ? WHERE worker_id IN (?)
|
||||
`, [departmentId, workerIds]);
|
||||
return result.affectedRows;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = departmentModel;
|
||||
@@ -9,10 +9,10 @@ const EquipmentModel = {
|
||||
const query = `
|
||||
INSERT INTO equipments (
|
||||
equipment_code, equipment_name, equipment_type, model_name,
|
||||
manufacturer, installation_date, serial_number, specifications,
|
||||
manufacturer, supplier, purchase_price, installation_date, serial_number, specifications,
|
||||
status, notes, workplace_id, map_x_percent, map_y_percent,
|
||||
map_width_percent, map_height_percent
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const values = [
|
||||
@@ -21,6 +21,8 @@ const EquipmentModel = {
|
||||
equipmentData.equipment_type || null,
|
||||
equipmentData.model_name || null,
|
||||
equipmentData.manufacturer || null,
|
||||
equipmentData.supplier || null,
|
||||
equipmentData.purchase_price || null,
|
||||
equipmentData.installation_date || null,
|
||||
equipmentData.serial_number || null,
|
||||
equipmentData.specifications || null,
|
||||
@@ -168,6 +170,8 @@ const EquipmentModel = {
|
||||
equipment_type = ?,
|
||||
model_name = ?,
|
||||
manufacturer = ?,
|
||||
supplier = ?,
|
||||
purchase_price = ?,
|
||||
installation_date = ?,
|
||||
serial_number = ?,
|
||||
specifications = ?,
|
||||
@@ -188,6 +192,8 @@ const EquipmentModel = {
|
||||
equipmentData.equipment_type || null,
|
||||
equipmentData.model_name || null,
|
||||
equipmentData.manufacturer || null,
|
||||
equipmentData.supplier || null,
|
||||
equipmentData.purchase_price || null,
|
||||
equipmentData.installation_date || null,
|
||||
equipmentData.serial_number || null,
|
||||
equipmentData.specifications || null,
|
||||
@@ -211,11 +217,24 @@ const EquipmentModel = {
|
||||
}
|
||||
},
|
||||
|
||||
// UPDATE MAP POSITION - 지도상 위치 업데이트
|
||||
// UPDATE MAP POSITION - 지도상 위치 업데이트 (선택적으로 workplace_id도 업데이트)
|
||||
updateMapPosition: async (equipmentId, positionData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
|
||||
// workplace_id가 포함된 경우 함께 업데이트
|
||||
const hasWorkplaceId = positionData.workplace_id !== undefined;
|
||||
|
||||
const query = hasWorkplaceId ? `
|
||||
UPDATE equipments SET
|
||||
workplace_id = ?,
|
||||
map_x_percent = ?,
|
||||
map_y_percent = ?,
|
||||
map_width_percent = ?,
|
||||
map_height_percent = ?,
|
||||
updated_at = NOW()
|
||||
WHERE equipment_id = ?
|
||||
` : `
|
||||
UPDATE equipments SET
|
||||
map_x_percent = ?,
|
||||
map_y_percent = ?,
|
||||
@@ -225,7 +244,14 @@ const EquipmentModel = {
|
||||
WHERE equipment_id = ?
|
||||
`;
|
||||
|
||||
const values = [
|
||||
const values = hasWorkplaceId ? [
|
||||
positionData.workplace_id,
|
||||
positionData.map_x_percent,
|
||||
positionData.map_y_percent,
|
||||
positionData.map_width_percent,
|
||||
positionData.map_height_percent,
|
||||
equipmentId
|
||||
] : [
|
||||
positionData.map_x_percent,
|
||||
positionData.map_y_percent,
|
||||
positionData.map_width_percent,
|
||||
@@ -294,6 +320,39 @@ const EquipmentModel = {
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
// GET NEXT EQUIPMENT CODE - 다음 관리번호 자동 생성 (TKP-001 형식)
|
||||
getNextEquipmentCode: async (prefix = 'TKP', callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
// 해당 접두사로 시작하는 가장 큰 번호 찾기
|
||||
const query = `
|
||||
SELECT equipment_code
|
||||
FROM equipments
|
||||
WHERE equipment_code LIKE ?
|
||||
ORDER BY equipment_code DESC
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query, [`${prefix}-%`]);
|
||||
|
||||
let nextNumber = 1;
|
||||
if (rows.length > 0) {
|
||||
// TKP-001 형식에서 숫자 부분 추출
|
||||
const lastCode = rows[0].equipment_code;
|
||||
const match = lastCode.match(new RegExp(`^${prefix}-(\\d+)$`));
|
||||
if (match) {
|
||||
nextNumber = parseInt(match[1], 10) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 3자리로 패딩 (001, 002, ...)
|
||||
const nextCode = `${prefix}-${String(nextNumber).padStart(3, '0')}`;
|
||||
callback(null, nextCode);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
358
api.hyungi.net/models/patrolModel.js
Normal file
358
api.hyungi.net/models/patrolModel.js
Normal file
@@ -0,0 +1,358 @@
|
||||
// patrolModel.js
|
||||
// 일일순회점검 시스템 모델
|
||||
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
const PatrolModel = {
|
||||
// ==================== 순회점검 세션 ====================
|
||||
|
||||
// 세션 생성 또는 조회
|
||||
getOrCreateSession: async (patrolDate, patrolTime, categoryId, inspectorId) => {
|
||||
const db = await getDb();
|
||||
|
||||
// 기존 세션 확인
|
||||
const [existingRows] = await db.query(`
|
||||
SELECT session_id, status, started_at, completed_at
|
||||
FROM daily_patrol_sessions
|
||||
WHERE patrol_date = ? AND patrol_time = ? AND category_id = ?
|
||||
`, [patrolDate, patrolTime, categoryId]);
|
||||
|
||||
if (existingRows.length > 0) {
|
||||
return existingRows[0];
|
||||
}
|
||||
|
||||
// 새 세션 생성
|
||||
const [result] = await db.query(`
|
||||
INSERT INTO daily_patrol_sessions (patrol_date, patrol_time, category_id, inspector_id, started_at)
|
||||
VALUES (?, ?, ?, ?, CURTIME())
|
||||
`, [patrolDate, patrolTime, categoryId, inspectorId]);
|
||||
|
||||
return {
|
||||
session_id: result.insertId,
|
||||
status: 'in_progress',
|
||||
started_at: new Date().toTimeString().slice(0, 8)
|
||||
};
|
||||
},
|
||||
|
||||
// 세션 조회
|
||||
getSession: async (sessionId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT s.*, u.name AS inspector_name, wc.category_name
|
||||
FROM daily_patrol_sessions s
|
||||
LEFT JOIN users u ON s.inspector_id = u.user_id
|
||||
LEFT JOIN workplace_categories wc ON s.category_id = wc.category_id
|
||||
WHERE s.session_id = ?
|
||||
`, [sessionId]);
|
||||
return rows[0] || null;
|
||||
},
|
||||
|
||||
// 세션 목록 조회
|
||||
getSessions: async (filters = {}) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT s.*, u.name AS inspector_name, wc.category_name,
|
||||
(SELECT COUNT(*) FROM patrol_check_records WHERE session_id = s.session_id AND is_checked = 1) AS checked_count,
|
||||
(SELECT COUNT(*) FROM patrol_check_records WHERE session_id = s.session_id) AS total_count
|
||||
FROM daily_patrol_sessions s
|
||||
LEFT JOIN users u ON s.inspector_id = u.user_id
|
||||
LEFT JOIN workplace_categories wc ON s.category_id = wc.category_id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const params = [];
|
||||
|
||||
if (filters.patrol_date) {
|
||||
query += ' AND s.patrol_date = ?';
|
||||
params.push(filters.patrol_date);
|
||||
}
|
||||
if (filters.patrol_time) {
|
||||
query += ' AND s.patrol_time = ?';
|
||||
params.push(filters.patrol_time);
|
||||
}
|
||||
if (filters.category_id) {
|
||||
query += ' AND s.category_id = ?';
|
||||
params.push(filters.category_id);
|
||||
}
|
||||
if (filters.status) {
|
||||
query += ' AND s.status = ?';
|
||||
params.push(filters.status);
|
||||
}
|
||||
|
||||
query += ' ORDER BY s.patrol_date DESC, s.patrol_time DESC';
|
||||
|
||||
if (filters.limit) {
|
||||
query += ' LIMIT ?';
|
||||
params.push(parseInt(filters.limit));
|
||||
}
|
||||
|
||||
const [rows] = await db.query(query, params);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// 세션 완료 처리
|
||||
completeSession: async (sessionId) => {
|
||||
const db = await getDb();
|
||||
await db.query(`
|
||||
UPDATE daily_patrol_sessions
|
||||
SET status = 'completed', completed_at = CURTIME(), updated_at = NOW()
|
||||
WHERE session_id = ?
|
||||
`, [sessionId]);
|
||||
return true;
|
||||
},
|
||||
|
||||
// 세션 메모 업데이트
|
||||
updateSessionNotes: async (sessionId, notes) => {
|
||||
const db = await getDb();
|
||||
await db.query(`
|
||||
UPDATE daily_patrol_sessions
|
||||
SET notes = ?, updated_at = NOW()
|
||||
WHERE session_id = ?
|
||||
`, [notes, sessionId]);
|
||||
return true;
|
||||
},
|
||||
|
||||
// ==================== 체크리스트 항목 ====================
|
||||
|
||||
// 체크리스트 항목 조회 (공장/작업장별 필터링)
|
||||
getChecklistItems: async (categoryId = null, workplaceId = null) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT *
|
||||
FROM patrol_checklist_items
|
||||
WHERE is_active = 1
|
||||
AND (workplace_id IS NULL OR workplace_id = ?)
|
||||
AND (category_id IS NULL OR category_id = ?)
|
||||
ORDER BY check_category, display_order, check_item
|
||||
`;
|
||||
const [rows] = await db.query(query, [workplaceId, categoryId]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// 체크리스트 항목 CRUD
|
||||
createChecklistItem: async (data) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`
|
||||
INSERT INTO patrol_checklist_items (workplace_id, category_id, check_category, check_item, description, display_order, is_required)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`, [data.workplace_id, data.category_id, data.check_category, data.check_item, data.description, data.display_order || 0, data.is_required !== false]);
|
||||
return result.insertId;
|
||||
},
|
||||
|
||||
updateChecklistItem: async (itemId, data) => {
|
||||
const db = await getDb();
|
||||
const fields = [];
|
||||
const params = [];
|
||||
|
||||
['workplace_id', 'category_id', 'check_category', 'check_item', 'description', 'display_order', 'is_required', 'is_active'].forEach(key => {
|
||||
if (data[key] !== undefined) {
|
||||
fields.push(`${key} = ?`);
|
||||
params.push(data[key]);
|
||||
}
|
||||
});
|
||||
|
||||
if (fields.length === 0) return false;
|
||||
|
||||
params.push(itemId);
|
||||
await db.query(`UPDATE patrol_checklist_items SET ${fields.join(', ')}, updated_at = NOW() WHERE item_id = ?`, params);
|
||||
return true;
|
||||
},
|
||||
|
||||
deleteChecklistItem: async (itemId) => {
|
||||
const db = await getDb();
|
||||
await db.query('UPDATE patrol_checklist_items SET is_active = 0, updated_at = NOW() WHERE item_id = ?', [itemId]);
|
||||
return true;
|
||||
},
|
||||
|
||||
// ==================== 체크 기록 ====================
|
||||
|
||||
// 작업장별 체크 기록 조회
|
||||
getCheckRecords: async (sessionId, workplaceId = null) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT r.*, ci.check_category, ci.check_item, ci.is_required
|
||||
FROM patrol_check_records r
|
||||
JOIN patrol_checklist_items ci ON r.check_item_id = ci.item_id
|
||||
WHERE r.session_id = ?
|
||||
`;
|
||||
const params = [sessionId];
|
||||
|
||||
if (workplaceId) {
|
||||
query += ' AND r.workplace_id = ?';
|
||||
params.push(workplaceId);
|
||||
}
|
||||
|
||||
query += ' ORDER BY ci.check_category, ci.display_order';
|
||||
|
||||
const [rows] = await db.query(query, params);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// 체크 기록 저장 (upsert)
|
||||
saveCheckRecord: async (sessionId, workplaceId, checkItemId, isChecked, checkResult = null, note = null) => {
|
||||
const db = await getDb();
|
||||
await db.query(`
|
||||
INSERT INTO patrol_check_records (session_id, workplace_id, check_item_id, is_checked, check_result, note, checked_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
is_checked = VALUES(is_checked),
|
||||
check_result = VALUES(check_result),
|
||||
note = VALUES(note),
|
||||
checked_at = NOW()
|
||||
`, [sessionId, workplaceId, checkItemId, isChecked, checkResult, note]);
|
||||
return true;
|
||||
},
|
||||
|
||||
// 여러 체크 기록 일괄 저장
|
||||
saveCheckRecords: async (sessionId, workplaceId, records) => {
|
||||
const db = await getDb();
|
||||
for (const record of records) {
|
||||
await db.query(`
|
||||
INSERT INTO patrol_check_records (session_id, workplace_id, check_item_id, is_checked, check_result, note, checked_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
is_checked = VALUES(is_checked),
|
||||
check_result = VALUES(check_result),
|
||||
note = VALUES(note),
|
||||
checked_at = NOW()
|
||||
`, [sessionId, workplaceId, record.check_item_id, record.is_checked, record.check_result, record.note]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// ==================== 작업장 물품 현황 ====================
|
||||
|
||||
// 작업장 물품 조회
|
||||
getWorkplaceItems: async (workplaceId, activeOnly = true) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT wi.*, u.name AS created_by_name, it.type_name, it.icon, it.color
|
||||
FROM workplace_items wi
|
||||
LEFT JOIN users u ON wi.created_by = u.user_id
|
||||
LEFT JOIN item_types it ON wi.item_type = it.type_code
|
||||
WHERE wi.workplace_id = ?
|
||||
`;
|
||||
if (activeOnly) {
|
||||
query += ' AND wi.is_active = 1';
|
||||
}
|
||||
query += ' ORDER BY wi.created_at DESC';
|
||||
|
||||
const [rows] = await db.query(query, [workplaceId]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// 물품 추가
|
||||
createWorkplaceItem: async (data) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`
|
||||
INSERT INTO workplace_items
|
||||
(workplace_id, patrol_session_id, project_id, item_type, item_name, quantity, x_percent, y_percent, width_percent, height_percent, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, [
|
||||
data.workplace_id,
|
||||
data.patrol_session_id,
|
||||
data.project_id,
|
||||
data.item_type,
|
||||
data.item_name,
|
||||
data.quantity || 1,
|
||||
data.x_percent,
|
||||
data.y_percent,
|
||||
data.width_percent,
|
||||
data.height_percent,
|
||||
data.created_by
|
||||
]);
|
||||
return result.insertId;
|
||||
},
|
||||
|
||||
// 물품 수정
|
||||
updateWorkplaceItem: async (itemId, data, userId) => {
|
||||
const db = await getDb();
|
||||
const fields = [];
|
||||
const params = [];
|
||||
|
||||
['item_type', 'item_name', 'quantity', 'x_percent', 'y_percent', 'width_percent', 'height_percent', 'is_active', 'project_id'].forEach(key => {
|
||||
if (data[key] !== undefined) {
|
||||
fields.push(`${key} = ?`);
|
||||
params.push(data[key]);
|
||||
}
|
||||
});
|
||||
|
||||
if (fields.length === 0) return false;
|
||||
|
||||
fields.push('updated_by = ?', 'updated_at = NOW()');
|
||||
params.push(userId, itemId);
|
||||
|
||||
await db.query(`UPDATE workplace_items SET ${fields.join(', ')} WHERE item_id = ?`, params);
|
||||
return true;
|
||||
},
|
||||
|
||||
// 물품 삭제 (비활성화)
|
||||
deleteWorkplaceItem: async (itemId, userId) => {
|
||||
const db = await getDb();
|
||||
await db.query('UPDATE workplace_items SET is_active = 0, updated_by = ?, updated_at = NOW() WHERE item_id = ?', [userId, itemId]);
|
||||
return true;
|
||||
},
|
||||
|
||||
// 물품 영구 삭제
|
||||
hardDeleteWorkplaceItem: async (itemId) => {
|
||||
const db = await getDb();
|
||||
await db.query('DELETE FROM workplace_items WHERE item_id = ?', [itemId]);
|
||||
return true;
|
||||
},
|
||||
|
||||
// ==================== 물품 유형 ====================
|
||||
|
||||
// 물품 유형 목록 조회
|
||||
getItemTypes: async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query('SELECT * FROM item_types WHERE is_active = 1 ORDER BY display_order');
|
||||
return rows;
|
||||
},
|
||||
|
||||
// ==================== 대시보드/통계 ====================
|
||||
|
||||
// 오늘 순회점검 현황
|
||||
getTodayPatrolStatus: async (categoryId = null) => {
|
||||
const db = await getDb();
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
|
||||
let query = `
|
||||
SELECT s.session_id, s.patrol_time, s.status, s.inspector_id, u.name AS inspector_name,
|
||||
s.started_at, s.completed_at,
|
||||
(SELECT COUNT(*) FROM patrol_check_records WHERE session_id = s.session_id AND is_checked = 1) AS checked_count,
|
||||
(SELECT COUNT(*) FROM patrol_check_records WHERE session_id = s.session_id) AS total_count
|
||||
FROM daily_patrol_sessions s
|
||||
LEFT JOIN users u ON s.inspector_id = u.user_id
|
||||
WHERE s.patrol_date = ?
|
||||
`;
|
||||
const params = [today];
|
||||
|
||||
if (categoryId) {
|
||||
query += ' AND s.category_id = ?';
|
||||
params.push(categoryId);
|
||||
}
|
||||
|
||||
query += ' ORDER BY s.patrol_time';
|
||||
|
||||
const [rows] = await db.query(query, params);
|
||||
return rows;
|
||||
},
|
||||
|
||||
// 작업장별 점검 현황 (세션 기준)
|
||||
getWorkplaceCheckStatus: async (sessionId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT w.workplace_id, w.workplace_name,
|
||||
COUNT(DISTINCT r.check_item_id) AS checked_count,
|
||||
(SELECT COUNT(*) FROM patrol_checklist_items WHERE is_active = 1) AS total_items,
|
||||
MAX(r.checked_at) AS last_check_time
|
||||
FROM workplaces w
|
||||
LEFT JOIN patrol_check_records r ON w.workplace_id = r.workplace_id AND r.session_id = ?
|
||||
WHERE w.is_active = 1
|
||||
GROUP BY w.workplace_id
|
||||
ORDER BY w.workplace_name
|
||||
`, [sessionId]);
|
||||
return rows;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = PatrolModel;
|
||||
@@ -20,14 +20,15 @@ const create = async (worker, callback) => {
|
||||
salary = null,
|
||||
annual_leave = null,
|
||||
status = 'active',
|
||||
employment_status = 'employed'
|
||||
employment_status = 'employed',
|
||||
department_id = null
|
||||
} = worker;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workers
|
||||
(worker_name, job_type, join_date, salary, annual_leave, status, employment_status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[worker_name, job_type, formatDate(join_date), salary, annual_leave, status, employment_status]
|
||||
(worker_name, job_type, join_date, salary, annual_leave, status, employment_status, department_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[worker_name, job_type, formatDate(join_date), salary, annual_leave, status, employment_status, department_id]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
@@ -45,9 +46,11 @@ const getAll = async (callback) => {
|
||||
SELECT
|
||||
w.*,
|
||||
CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active,
|
||||
u.user_id
|
||||
u.user_id,
|
||||
d.department_name
|
||||
FROM workers w
|
||||
LEFT JOIN users u ON w.worker_id = u.worker_id
|
||||
LEFT JOIN departments d ON w.department_id = d.department_id
|
||||
ORDER BY w.worker_id DESC
|
||||
`);
|
||||
callback(null, rows);
|
||||
@@ -64,9 +67,11 @@ const getById = async (worker_id, callback) => {
|
||||
SELECT
|
||||
w.*,
|
||||
CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active,
|
||||
u.user_id
|
||||
u.user_id,
|
||||
d.department_name
|
||||
FROM workers w
|
||||
LEFT JOIN users u ON w.worker_id = u.worker_id
|
||||
LEFT JOIN departments d ON w.department_id = d.department_id
|
||||
WHERE w.worker_id = ?
|
||||
`, [worker_id]);
|
||||
callback(null, rows[0]);
|
||||
@@ -87,7 +92,8 @@ const update = async (worker, callback) => {
|
||||
join_date,
|
||||
salary,
|
||||
annual_leave,
|
||||
employment_status
|
||||
employment_status,
|
||||
department_id
|
||||
} = worker;
|
||||
|
||||
// 업데이트할 필드만 동적으로 구성
|
||||
@@ -122,6 +128,10 @@ const update = async (worker, callback) => {
|
||||
updates.push('employment_status = ?');
|
||||
values.push(employment_status);
|
||||
}
|
||||
if (department_id !== undefined) {
|
||||
updates.push('department_id = ?');
|
||||
values.push(department_id);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
callback(new Error('업데이트할 필드가 없습니다'));
|
||||
|
||||
@@ -162,7 +162,7 @@ const getAllWorkplaces = async (callback) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.workplace_purpose, w.display_priority, w.created_at, w.updated_at,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
@@ -182,7 +182,7 @@ const getActiveWorkplaces = async (callback) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.created_at, w.updated_at,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
@@ -203,7 +203,7 @@ const getWorkplacesByCategory = async (categoryId, callback) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.created_at, w.updated_at,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
@@ -225,7 +225,7 @@ const getWorkplaceById = async (workplaceId, callback) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.created_at, w.updated_at,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
@@ -250,15 +250,16 @@ const updateWorkplace = async (workplaceId, workplace, callback) => {
|
||||
description,
|
||||
is_active,
|
||||
workplace_purpose,
|
||||
display_priority
|
||||
display_priority,
|
||||
layout_image
|
||||
} = workplace;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplaces
|
||||
SET category_id = ?, workplace_name = ?, description = ?, is_active = ?,
|
||||
workplace_purpose = ?, display_priority = ?, updated_at = NOW()
|
||||
workplace_purpose = ?, display_priority = ?, layout_image = ?, updated_at = NOW()
|
||||
WHERE workplace_id = ?`,
|
||||
[category_id, workplace_name, description, is_active, workplace_purpose, display_priority, workplaceId]
|
||||
[category_id, workplace_name, description, is_active, workplace_purpose, display_priority, layout_image, workplaceId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
|
||||
Reference in New Issue
Block a user