feat: 설비 관리 시스템 구축
## 주요 기능 - 설비 등록/수정/삭제 기능 - 작업장별 설비 연결 - 작업장 지도에서 설비 위치 정의 - 필터링 및 검색 기능 ## 백엔드 - equipments 테이블 생성 (마이그레이션) - 설비 API (모델, 컨트롤러, 라우트) 구현 - workplaces 테이블에 layout_image 컬럼 추가 ## 프론트엔드 - 설비 관리 페이지 (equipments.html) - 설비 관리 JavaScript (equipment-management.js) - 작업장 지도 모달 개선 ## 버그 수정 - 카테고리/작업장 이미지 보존 로직 개선 (null 처리) - 작업장 레이아웃 이미지 업로드 경로 수정 (public/uploads → uploads) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
300
api.hyungi.net/models/equipmentModel.js
Normal file
300
api.hyungi.net/models/equipmentModel.js
Normal file
@@ -0,0 +1,300 @@
|
||||
// models/equipmentModel.js
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
const EquipmentModel = {
|
||||
// CREATE - 설비 생성
|
||||
create: async (equipmentData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
INSERT INTO equipments (
|
||||
equipment_code, equipment_name, equipment_type, model_name,
|
||||
manufacturer, installation_date, serial_number, specifications,
|
||||
status, notes, workplace_id, map_x_percent, map_y_percent,
|
||||
map_width_percent, map_height_percent
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const values = [
|
||||
equipmentData.equipment_code,
|
||||
equipmentData.equipment_name,
|
||||
equipmentData.equipment_type || null,
|
||||
equipmentData.model_name || null,
|
||||
equipmentData.manufacturer || null,
|
||||
equipmentData.installation_date || null,
|
||||
equipmentData.serial_number || null,
|
||||
equipmentData.specifications || null,
|
||||
equipmentData.status || 'active',
|
||||
equipmentData.notes || null,
|
||||
equipmentData.workplace_id || null,
|
||||
equipmentData.map_x_percent || null,
|
||||
equipmentData.map_y_percent || null,
|
||||
equipmentData.map_width_percent || null,
|
||||
equipmentData.map_height_percent || null
|
||||
];
|
||||
|
||||
const [result] = await db.query(query, values);
|
||||
callback(null, {
|
||||
equipment_id: result.insertId,
|
||||
...equipmentData
|
||||
});
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
// READ ALL - 모든 설비 조회 (필터링 옵션 포함)
|
||||
getAll: async (filters, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
e.*,
|
||||
w.workplace_name,
|
||||
wc.category_name
|
||||
FROM equipments e
|
||||
LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE 1=1
|
||||
`;
|
||||
|
||||
const values = [];
|
||||
|
||||
// 필터링: 작업장 ID
|
||||
if (filters.workplace_id) {
|
||||
query += ' AND e.workplace_id = ?';
|
||||
values.push(filters.workplace_id);
|
||||
}
|
||||
|
||||
// 필터링: 설비 유형
|
||||
if (filters.equipment_type) {
|
||||
query += ' AND e.equipment_type = ?';
|
||||
values.push(filters.equipment_type);
|
||||
}
|
||||
|
||||
// 필터링: 상태
|
||||
if (filters.status) {
|
||||
query += ' AND e.status = ?';
|
||||
values.push(filters.status);
|
||||
}
|
||||
|
||||
// 필터링: 검색어 (설비명, 설비코드)
|
||||
if (filters.search) {
|
||||
query += ' AND (e.equipment_name LIKE ? OR e.equipment_code LIKE ?)';
|
||||
const searchTerm = `%${filters.search}%`;
|
||||
values.push(searchTerm, searchTerm);
|
||||
}
|
||||
|
||||
query += ' ORDER BY e.equipment_code ASC';
|
||||
|
||||
const [rows] = await db.query(query, values);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
// READ ONE - 특정 설비 조회
|
||||
getById: async (equipmentId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
e.*,
|
||||
w.workplace_name,
|
||||
wc.category_name
|
||||
FROM equipments e
|
||||
LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE e.equipment_id = ?
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query, [equipmentId]);
|
||||
callback(null, rows[0]);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
// READ BY WORKPLACE - 특정 작업장의 설비 조회
|
||||
getByWorkplace: async (workplaceId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT e.*
|
||||
FROM equipments e
|
||||
WHERE e.workplace_id = ?
|
||||
ORDER BY e.equipment_code ASC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query, [workplaceId]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
// READ ACTIVE - 활성 설비만 조회
|
||||
getActive: async (callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
e.*,
|
||||
w.workplace_name,
|
||||
wc.category_name
|
||||
FROM equipments e
|
||||
LEFT JOIN workplaces w ON e.workplace_id = w.workplace_id
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE e.status = 'active'
|
||||
ORDER BY e.equipment_code ASC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
// UPDATE - 설비 수정
|
||||
update: async (equipmentId, equipmentData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
UPDATE equipments SET
|
||||
equipment_code = ?,
|
||||
equipment_name = ?,
|
||||
equipment_type = ?,
|
||||
model_name = ?,
|
||||
manufacturer = ?,
|
||||
installation_date = ?,
|
||||
serial_number = ?,
|
||||
specifications = ?,
|
||||
status = ?,
|
||||
notes = ?,
|
||||
workplace_id = ?,
|
||||
map_x_percent = ?,
|
||||
map_y_percent = ?,
|
||||
map_width_percent = ?,
|
||||
map_height_percent = ?,
|
||||
updated_at = NOW()
|
||||
WHERE equipment_id = ?
|
||||
`;
|
||||
|
||||
const values = [
|
||||
equipmentData.equipment_code,
|
||||
equipmentData.equipment_name,
|
||||
equipmentData.equipment_type || null,
|
||||
equipmentData.model_name || null,
|
||||
equipmentData.manufacturer || null,
|
||||
equipmentData.installation_date || null,
|
||||
equipmentData.serial_number || null,
|
||||
equipmentData.specifications || null,
|
||||
equipmentData.status || 'active',
|
||||
equipmentData.notes || null,
|
||||
equipmentData.workplace_id || null,
|
||||
equipmentData.map_x_percent || null,
|
||||
equipmentData.map_y_percent || null,
|
||||
equipmentData.map_width_percent || null,
|
||||
equipmentData.map_height_percent || null,
|
||||
equipmentId
|
||||
];
|
||||
|
||||
const [result] = await db.query(query, values);
|
||||
if (result.affectedRows === 0) {
|
||||
return callback(new Error('Equipment not found'));
|
||||
}
|
||||
callback(null, { equipment_id: equipmentId, ...equipmentData });
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
// UPDATE MAP POSITION - 지도상 위치 업데이트
|
||||
updateMapPosition: async (equipmentId, positionData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
UPDATE equipments SET
|
||||
map_x_percent = ?,
|
||||
map_y_percent = ?,
|
||||
map_width_percent = ?,
|
||||
map_height_percent = ?,
|
||||
updated_at = NOW()
|
||||
WHERE equipment_id = ?
|
||||
`;
|
||||
|
||||
const values = [
|
||||
positionData.map_x_percent,
|
||||
positionData.map_y_percent,
|
||||
positionData.map_width_percent,
|
||||
positionData.map_height_percent,
|
||||
equipmentId
|
||||
];
|
||||
|
||||
const [result] = await db.query(query, values);
|
||||
if (result.affectedRows === 0) {
|
||||
return callback(new Error('Equipment not found'));
|
||||
}
|
||||
callback(null, { equipment_id: equipmentId, ...positionData });
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
// DELETE - 설비 삭제
|
||||
delete: async (equipmentId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = 'DELETE FROM equipments WHERE equipment_id = ?';
|
||||
|
||||
const [result] = await db.query(query, [equipmentId]);
|
||||
if (result.affectedRows === 0) {
|
||||
return callback(new Error('Equipment not found'));
|
||||
}
|
||||
callback(null, { equipment_id: equipmentId });
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
// CHECK DUPLICATE CODE - 설비 코드 중복 확인
|
||||
checkDuplicateCode: async (equipmentCode, excludeEquipmentId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
let query = 'SELECT equipment_id FROM equipments WHERE equipment_code = ?';
|
||||
const values = [equipmentCode];
|
||||
|
||||
if (excludeEquipmentId) {
|
||||
query += ' AND equipment_id != ?';
|
||||
values.push(excludeEquipmentId);
|
||||
}
|
||||
|
||||
const [rows] = await db.query(query, values);
|
||||
callback(null, rows.length > 0);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
// GET EQUIPMENT TYPES - 사용 중인 설비 유형 목록 조회
|
||||
getEquipmentTypes: async (callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT DISTINCT equipment_type
|
||||
FROM equipments
|
||||
WHERE equipment_type IS NOT NULL
|
||||
ORDER BY equipment_type ASC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(query);
|
||||
callback(null, rows.map(row => row.equipment_type));
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = EquipmentModel;
|
||||
Reference in New Issue
Block a user