- 일일순회점검 시스템 신규 구현 - 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>
360 lines
10 KiB
JavaScript
360 lines
10 KiB
JavaScript
// 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, 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`;
|
|
|
|
const values = [
|
|
equipmentData.equipment_code,
|
|
equipmentData.equipment_name,
|
|
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,
|
|
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 = ?,
|
|
supplier = ?,
|
|
purchase_price = ?,
|
|
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.supplier || null,
|
|
equipmentData.purchase_price || 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 - 지도상 위치 업데이트 (선택적으로 workplace_id도 업데이트)
|
|
updateMapPosition: async (equipmentId, positionData, callback) => {
|
|
try {
|
|
const db = await getDb();
|
|
|
|
// 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 = ?,
|
|
map_width_percent = ?,
|
|
map_height_percent = ?,
|
|
updated_at = NOW()
|
|
WHERE equipment_id = ?
|
|
`;
|
|
|
|
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,
|
|
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);
|
|
}
|
|
},
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
};
|
|
|
|
module.exports = EquipmentModel;
|