refactor: System 1 모델/컨트롤러 콜백→async/await 전환

11개 모델 파일의 171개 콜백 메서드를 직접 return/throw 패턴으로 변환.
8개 컨트롤러에서 new Promise 래퍼와 중첩 콜백 제거, console.error→logger.error 교체.
미사용 pageAccessModel.js 삭제. 전체 -3,600줄 감소.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-25 09:40:33 +09:00
parent c29a1506bb
commit 5183e9ff85
22 changed files with 4791 additions and 8406 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@
const vacationBalanceModel = require('../models/vacationBalanceModel'); const vacationBalanceModel = require('../models/vacationBalanceModel');
const vacationTypeModel = require('../models/vacationTypeModel'); const vacationTypeModel = require('../models/vacationTypeModel');
const logger = require('../utils/logger');
const vacationBalanceController = { const vacationBalanceController = {
/** /**
@@ -14,27 +15,11 @@ const vacationBalanceController = {
async getByWorkerAndYear(req, res) { async getByWorkerAndYear(req, res) {
try { try {
const { workerId, year } = req.params; const { workerId, year } = req.params;
const results = await vacationBalanceModel.getByWorkerAndYear(workerId, year);
vacationBalanceModel.getByWorkerAndYear(workerId, year, (err, results) => { res.json({ success: true, data: results });
if (err) {
console.error('휴가 잔액 조회 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 잔액을 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getByWorkerAndYear 오류:', error); logger.error('휴가 잔액 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '휴가 잔액을 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -45,27 +30,11 @@ const vacationBalanceController = {
async getAllByYear(req, res) { async getAllByYear(req, res) {
try { try {
const { year } = req.params; const { year } = req.params;
const results = await vacationBalanceModel.getAllByYear(year);
vacationBalanceModel.getAllByYear(year, (err, results) => { res.json({ success: true, data: results });
if (err) {
console.error('전체 휴가 잔액 조회 오류:', err);
return res.status(500).json({
success: false,
message: '전체 휴가 잔액을 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getAllByYear 오류:', error); logger.error('전체 휴가 잔액 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '전체 휴가 잔액을 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -75,17 +44,9 @@ const vacationBalanceController = {
*/ */
async createBalance(req, res) { async createBalance(req, res) {
try { try {
const { const { worker_id, vacation_type_id, year, total_days, used_days, notes } = req.body;
worker_id,
vacation_type_id,
year,
total_days,
used_days,
notes
} = req.body;
const created_by = req.user.user_id; const created_by = req.user.user_id;
// 필수 필드 검증
if (!worker_id || !vacation_type_id || !year || total_days === undefined) { if (!worker_id || !vacation_type_id || !year || total_days === undefined) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
@@ -94,54 +55,33 @@ const vacationBalanceController = {
} }
// 중복 체크 // 중복 체크
vacationBalanceModel.getByWorkerTypeYear(worker_id, vacation_type_id, year, (err, existing) => { const existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, vacation_type_id, year);
if (err) { if (existing && existing.length > 0) {
console.error('중복 체크 오류:', err); return res.status(400).json({
return res.status(500).json({ success: false,
success: false, message: '이미 해당 작업자의 해당 연도 휴가 잔액이 존재합니다'
message: '중복 체크 중 오류가 발생했습니다'
});
}
if (existing && existing.length > 0) {
return res.status(400).json({
success: false,
message: '이미 해당 작업자의 해당 연도 휴가 잔액이 존재합니다'
});
}
const balanceData = {
worker_id,
vacation_type_id,
year,
total_days,
used_days: used_days || 0,
notes: notes || null,
created_by
};
vacationBalanceModel.create(balanceData, (err, result) => {
if (err) {
console.error('휴가 잔액 생성 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 잔액을 생성하는 중 오류가 발생했습니다'
});
}
res.status(201).json({
success: true,
message: '휴가 잔액이 생성되었습니다',
data: { id: result.insertId }
});
}); });
}
const balanceData = {
worker_id,
vacation_type_id,
year,
total_days,
used_days: used_days || 0,
notes: notes || null,
created_by
};
const result = await vacationBalanceModel.create(balanceData);
res.status(201).json({
success: true,
message: '휴가 잔액이 생성되었습니다',
data: { id: result.insertId }
}); });
} catch (error) { } catch (error) {
console.error('createBalance 오류:', error); logger.error('휴가 잔액 생성 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -161,39 +101,18 @@ const vacationBalanceController = {
updateData.updated_at = new Date(); updateData.updated_at = new Date();
if (Object.keys(updateData).length === 1) { if (Object.keys(updateData).length === 1) {
return res.status(400).json({ return res.status(400).json({ success: false, message: '수정할 데이터가 없습니다' });
success: false,
message: '수정할 데이터가 없습니다'
});
} }
vacationBalanceModel.update(id, updateData, (err, result) => { const result = await vacationBalanceModel.update(id, updateData);
if (err) { if (result.affectedRows === 0) {
console.error('휴가 잔액 수정 오류:', err); return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' });
return res.status(500).json({ }
success: false,
message: '휴가 잔액을 수정하는 중 오류가 발생했습니다'
});
}
if (result.affectedRows === 0) { res.json({ success: true, message: '휴가 잔액이 수정되었습니다' });
return res.status(404).json({
success: false,
message: '휴가 잔액을 찾을 수 없습니다'
});
}
res.json({
success: true,
message: '휴가 잔액이 수정되었습니다'
});
});
} catch (error) { } catch (error) {
console.error('updateBalance 오류:', error); logger.error('휴가 잔액 수정 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -204,34 +123,16 @@ const vacationBalanceController = {
async deleteBalance(req, res) { async deleteBalance(req, res) {
try { try {
const { id } = req.params; const { id } = req.params;
const result = await vacationBalanceModel.delete(id);
vacationBalanceModel.delete(id, (err, result) => { if (result.affectedRows === 0) {
if (err) { return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' });
console.error('휴가 잔액 삭제 오류:', err); }
return res.status(500).json({
success: false,
message: '휴가 잔액을 삭제하는 중 오류가 발생했습니다'
});
}
if (result.affectedRows === 0) { res.json({ success: true, message: '휴가 잔액이 삭제되었습니다' });
return res.status(404).json({
success: false,
message: '휴가 잔액을 찾을 수 없습니다'
});
}
res.json({
success: true,
message: '휴가 잔액이 삭제되었습니다'
});
});
} catch (error) { } catch (error) {
console.error('deleteBalance 오류:', error); logger.error('휴가 잔액 삭제 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -251,74 +152,44 @@ const vacationBalanceController = {
}); });
} }
// 연차 일수 계산
const annualDays = vacationBalanceModel.calculateAnnualLeaveDays(hire_date, year); const annualDays = vacationBalanceModel.calculateAnnualLeaveDays(hire_date, year);
// ANNUAL 휴가 유형 ID 조회 // ANNUAL 휴가 유형 ID 조회
vacationTypeModel.getByCode('ANNUAL', (err, types) => { const types = await vacationTypeModel.getByCode('ANNUAL');
if (err || !types || types.length === 0) { if (!types || types.length === 0) {
console.error('ANNUAL 휴가 유형 조회 오류:', err); return res.status(500).json({ success: false, message: 'ANNUAL 휴가 유형을 찾을 수 없습니다' });
return res.status(500).json({ }
success: false,
message: 'ANNUAL 휴가 유형을 찾을 수 없습니다'
});
}
const annualTypeId = types[0].id; const annualTypeId = types[0].id;
// 중복 체크 // 중복 체크
vacationBalanceModel.getByWorkerTypeYear(worker_id, annualTypeId, year, (err, existing) => { const existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, annualTypeId, year);
if (err) { if (existing && existing.length > 0) {
console.error('중복 체크 오류:', err); return res.status(400).json({
return res.status(500).json({ success: false,
success: false, message: '이미 해당 작업자의 해당 연도 연차가 존재합니다'
message: '중복 체크 중 오류가 발생했습니다'
});
}
if (existing && existing.length > 0) {
return res.status(400).json({
success: false,
message: '이미 해당 작업자의 해당 연도 연차가 존재합니다'
});
}
const balanceData = {
worker_id,
vacation_type_id: annualTypeId,
year,
total_days: annualDays,
used_days: 0,
notes: `근속년수 기반 자동 계산 (입사일: ${hire_date})`,
created_by
};
vacationBalanceModel.create(balanceData, (err, result) => {
if (err) {
console.error('휴가 잔액 생성 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 잔액을 생성하는 중 오류가 발생했습니다'
});
}
res.status(201).json({
success: true,
message: `${annualDays}일의 연차가 자동으로 생성되었습니다`,
data: {
id: result.insertId,
calculated_days: annualDays
}
});
});
}); });
}
const balanceData = {
worker_id,
vacation_type_id: annualTypeId,
year,
total_days: annualDays,
used_days: 0,
notes: `근속년수 기반 자동 계산 (입사일: ${hire_date})`,
created_by
};
const result = await vacationBalanceModel.create(balanceData);
res.status(201).json({
success: true,
message: `${annualDays}일의 연차가 자동으로 생성되었습니다`,
data: { id: result.insertId, calculated_days: annualDays }
}); });
} catch (error) { } catch (error) {
console.error('autoCalculateAndCreate 오류:', error); logger.error('연차 자동 계산 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -332,10 +203,7 @@ const vacationBalanceController = {
const created_by = req.user.user_id; const created_by = req.user.user_id;
if (!balances || !Array.isArray(balances) || balances.length === 0) { if (!balances || !Array.isArray(balances) || balances.length === 0) {
return res.status(400).json({ return res.status(400).json({ success: false, message: '저장할 데이터가 없습니다' });
success: false,
message: '저장할 데이터가 없습니다'
});
} }
const { getDb } = require('../dbPool'); const { getDb } = require('../dbPool');
@@ -353,7 +221,6 @@ const vacationBalanceController = {
} }
try { try {
// Upsert 쿼리
const query = ` const query = `
INSERT INTO vacation_balance_details INSERT INTO vacation_balance_details
(worker_id, vacation_type_id, year, total_days, used_days, notes, created_by) (worker_id, vacation_type_id, year, total_days, used_days, notes, created_by)
@@ -367,7 +234,7 @@ const vacationBalanceController = {
await db.query(query, [worker_id, vacation_type_id, year, total_days, notes || null, created_by]); await db.query(query, [worker_id, vacation_type_id, year, total_days, notes || null, created_by]);
successCount++; successCount++;
} catch (err) { } catch (err) {
console.error('휴가 잔액 저장 오류:', err); logger.error('휴가 잔액 저장 오류:', err);
errorCount++; errorCount++;
} }
} }
@@ -378,11 +245,8 @@ const vacationBalanceController = {
data: { successCount, errorCount } data: { successCount, errorCount }
}); });
} catch (error) { } catch (error) {
console.error('bulkUpsert 오류:', error); logger.error('bulkUpsert 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -393,27 +257,11 @@ const vacationBalanceController = {
async getAvailableDays(req, res) { async getAvailableDays(req, res) {
try { try {
const { workerId, year } = req.params; const { workerId, year } = req.params;
const results = await vacationBalanceModel.getAvailableVacationDays(workerId, year);
vacationBalanceModel.getAvailableVacationDays(workerId, year, (err, results) => { res.json({ success: true, data: results });
if (err) {
console.error('사용 가능 휴가 조회 오류:', err);
return res.status(500).json({
success: false,
message: '사용 가능 휴가를 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getAvailableDays 오류:', error); logger.error('사용 가능 휴가 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '사용 가능 휴가를 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
} }
}; };

View File

@@ -4,6 +4,7 @@
*/ */
const vacationTypeModel = require('../models/vacationTypeModel'); const vacationTypeModel = require('../models/vacationTypeModel');
const logger = require('../utils/logger');
const vacationTypeController = { const vacationTypeController = {
/** /**
@@ -12,26 +13,11 @@ const vacationTypeController = {
*/ */
async getAllTypes(req, res) { async getAllTypes(req, res) {
try { try {
vacationTypeModel.getAll((err, results) => { const results = await vacationTypeModel.getAll();
if (err) { res.json({ success: true, data: results });
console.error('휴가 유형 조회 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 유형을 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getAllTypes 오류:', error); logger.error('휴가 유형 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '휴가 유형을 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -41,26 +27,11 @@ const vacationTypeController = {
*/ */
async getSystemTypes(req, res) { async getSystemTypes(req, res) {
try { try {
vacationTypeModel.getSystemTypes((err, results) => { const results = await vacationTypeModel.getSystemTypes();
if (err) { res.json({ success: true, data: results });
console.error('시스템 휴가 유형 조회 오류:', err);
return res.status(500).json({
success: false,
message: '시스템 휴가 유형을 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getSystemTypes 오류:', error); logger.error('시스템 휴가 유형 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '시스템 휴가 유형을 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -70,26 +41,11 @@ const vacationTypeController = {
*/ */
async getSpecialTypes(req, res) { async getSpecialTypes(req, res) {
try { try {
vacationTypeModel.getSpecialTypes((err, results) => { const results = await vacationTypeModel.getSpecialTypes();
if (err) { res.json({ success: true, data: results });
console.error('특별 휴가 유형 조회 오류:', err);
return res.status(500).json({
success: false,
message: '특별 휴가 유형을 조회하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
data: results
});
});
} catch (error) { } catch (error) {
console.error('getSpecialTypes 오류:', error); logger.error('특별 휴가 유형 조회 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '특별 휴가 유형을 조회하는 중 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -99,15 +55,8 @@ const vacationTypeController = {
*/ */
async createType(req, res) { async createType(req, res) {
try { try {
const { const { type_code, type_name, deduct_days, priority, description } = req.body;
type_code,
type_name,
deduct_days,
priority,
description
} = req.body;
// 필수 필드 검증
if (!type_code || !type_name || !deduct_days) { if (!type_code || !type_name || !deduct_days) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
@@ -116,56 +65,31 @@ const vacationTypeController = {
} }
// type_code 중복 체크 // type_code 중복 체크
vacationTypeModel.getByCode(type_code, (err, existingTypes) => { const existingTypes = await vacationTypeModel.getByCode(type_code);
if (err) { if (existingTypes && existingTypes.length > 0) {
console.error('type_code 중복 체크 오류:', err); return res.status(400).json({ success: false, message: '이미 존재하는 type_code입니다' });
return res.status(500).json({ }
success: false,
message: 'type_code 중복 체크 중 오류가 발생했습니다'
});
}
if (existingTypes && existingTypes.length > 0) { const typeData = {
return res.status(400).json({ type_code,
success: false, type_name,
message: '이미 존재하는 type_code입니다' deduct_days,
}); priority: priority || 50,
} description: description || null,
is_special: true,
is_system: false,
is_active: true
};
// 특별 휴가 유형으로 생성 const result = await vacationTypeModel.create(typeData);
const typeData = { res.status(201).json({
type_code, success: true,
type_name, message: '특별 휴가 유형이 생성되었습니다',
deduct_days, data: { id: result.insertId }
priority: priority || 50,
description: description || null,
is_special: true,
is_system: false,
is_active: true
};
vacationTypeModel.create(typeData, (err, result) => {
if (err) {
console.error('휴가 유형 생성 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 유형을 생성하는 중 오류가 발생했습니다'
});
}
res.status(201).json({
success: true,
message: '특별 휴가 유형이 생성되었습니다',
data: { id: result.insertId }
});
});
}); });
} catch (error) { } catch (error) {
console.error('createType 오류:', error); logger.error('휴가 유형 생성 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -176,78 +100,37 @@ const vacationTypeController = {
async updateType(req, res) { async updateType(req, res) {
try { try {
const { id } = req.params; const { id } = req.params;
const { const { type_name, deduct_days, priority, description, is_active } = req.body;
type_name,
deduct_days,
priority,
description,
is_active
} = req.body;
// 먼저 해당 유형 조회 const types = await vacationTypeModel.getById(id);
vacationTypeModel.getById(id, (err, types) => { if (!types || types.length === 0) {
if (err) { return res.status(404).json({ success: false, message: '휴가 유형을 찾을 수 없습니다' });
console.error('휴가 유형 조회 오류:', err); }
return res.status(500).json({
success: false,
message: '휴가 유형을 조회하는 중 오류가 발생했습니다'
});
}
if (!types || types.length === 0) { const type = types[0];
return res.status(404).json({ const updateData = {};
success: false,
message: '휴가 유형을 찾을 수 없습니다'
});
}
const type = types[0]; if (type.is_system) {
if (priority !== undefined) updateData.priority = priority;
if (description !== undefined) updateData.description = description;
} else {
if (type_name) updateData.type_name = type_name;
if (deduct_days !== undefined) updateData.deduct_days = deduct_days;
if (priority !== undefined) updateData.priority = priority;
if (description !== undefined) updateData.description = description;
if (is_active !== undefined) updateData.is_active = is_active;
}
// 시스템 기본 휴가의 경우 제한적으로만 수정 가능 if (Object.keys(updateData).length === 0) {
const updateData = {}; return res.status(400).json({ success: false, message: '수정할 데이터가 없습니다' });
if (type.is_system) { }
// 시스템 휴가는 priority와 description만 수정 가능
if (priority !== undefined) updateData.priority = priority;
if (description !== undefined) updateData.description = description;
} else {
// 특별 휴가는 모든 필드 수정 가능
if (type_name) updateData.type_name = type_name;
if (deduct_days !== undefined) updateData.deduct_days = deduct_days;
if (priority !== undefined) updateData.priority = priority;
if (description !== undefined) updateData.description = description;
if (is_active !== undefined) updateData.is_active = is_active;
}
if (Object.keys(updateData).length === 0) { updateData.updated_at = new Date();
return res.status(400).json({ await vacationTypeModel.update(id, updateData);
success: false, res.json({ success: true, message: '휴가 유형이 수정되었습니다' });
message: '수정할 데이터가 없습니다'
});
}
updateData.updated_at = new Date();
vacationTypeModel.update(id, updateData, (err, result) => {
if (err) {
console.error('휴가 유형 수정 오류:', err);
return res.status(500).json({
success: false,
message: '휴가 유형을 수정하는 중 오류가 발생했습니다'
});
}
res.json({
success: true,
message: '휴가 유형이 수정되었습니다'
});
});
});
} catch (error) { } catch (error) {
console.error('updateType 오류:', error); logger.error('휴가 유형 수정 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -258,34 +141,19 @@ const vacationTypeController = {
async deleteType(req, res) { async deleteType(req, res) {
try { try {
const { id } = req.params; const { id } = req.params;
const result = await vacationTypeModel.delete(id);
vacationTypeModel.delete(id, (err, result) => { if (result.affectedRows === 0) {
if (err) { return res.status(400).json({
console.error('휴가 유형 삭제 오류:', err); success: false,
return res.status(500).json({ message: '삭제할 수 없습니다. 시스템 기본 휴가이거나 존재하지 않는 휴가 유형입니다'
success: false,
message: '휴가 유형을 삭제하는 중 오류가 발생했습니다'
});
}
if (result.affectedRows === 0) {
return res.status(400).json({
success: false,
message: '삭제할 수 없습니다. 시스템 기본 휴가이거나 존재하지 않는 휴가 유형입니다'
});
}
res.json({
success: true,
message: '휴가 유형이 삭제되었습니다'
}); });
}); }
res.json({ success: true, message: '휴가 유형이 삭제되었습니다' });
} catch (error) { } catch (error) {
console.error('deleteType 오류:', error); logger.error('휴가 유형 삭제 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
}, },
@@ -297,35 +165,22 @@ const vacationTypeController = {
try { try {
const { priorities } = req.body; const { priorities } = req.body;
// priorities = [{ id: 1, priority: 10 }, { id: 2, priority: 20 }, ...]
if (!priorities || !Array.isArray(priorities)) { if (!priorities || !Array.isArray(priorities)) {
return res.status(400).json({ return res.status(400).json({ success: false, message: 'priorities 배열이 필요합니다' });
success: false,
message: 'priorities 배열이 필요합니다'
});
} }
vacationTypeModel.updatePriorities(priorities, (err, result) => { for (const { id, priority } of priorities) {
if (err) { await vacationTypeModel.updatePriority(id, priority);
console.error('우선순위 업데이트 오류:', err); }
return res.status(500).json({
success: false,
message: '우선순위를 업데이트하는 중 오류가 발생했습니다'
});
}
res.json({ res.json({
success: true, success: true,
message: '우선순위가 업데이트되었습니다', message: '우선순위가 업데이트되었습니다',
data: { updated: result.affectedRows } data: { updated: priorities.length }
});
}); });
} catch (error) { } catch (error) {
console.error('updatePriorities 오류:', error); logger.error('우선순위 업데이트 오류:', error);
res.status(500).json({ res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
success: false,
message: '서버 오류가 발생했습니다'
});
} }
} }
}; };

View File

@@ -1,555 +1,284 @@
const visitRequestModel = require('../models/visitRequestModel'); const visitRequestModel = require('../models/visitRequestModel');
const logger = require('../utils/logger');
// ==================== 출입 신청 관리 ==================== // ==================== 출입 신청 관리 ====================
/** exports.createVisitRequest = async (req, res) => {
* 출입 신청 생성 try {
*/ const requester_id = req.user.user_id;
exports.createVisitRequest = (req, res) => { const requestData = { requester_id, ...req.body };
const requester_id = req.user.user_id;
const requestData = {
requester_id,
...req.body
};
// 필수 필드 검증 const requiredFields = ['visitor_company', 'category_id', 'workplace_id', 'visit_date', 'visit_time', 'purpose_id'];
const requiredFields = ['visitor_company', 'category_id', 'workplace_id', 'visit_date', 'visit_time', 'purpose_id']; for (const field of requiredFields) {
for (const field of requiredFields) { if (!requestData[field]) {
if (!requestData[field]) { return res.status(400).json({ success: false, message: `${field}는 필수 입력 항목입니다.` });
return res.status(400).json({ }
success: false,
message: `${field}는 필수 입력 항목입니다.`
});
}
}
visitRequestModel.createVisitRequest(requestData, (err, requestId) => {
if (err) {
console.error('출입 신청 생성 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 생성 중 오류가 발생했습니다.',
error: err.message
});
} }
const requestId = await visitRequestModel.createVisitRequest(requestData);
res.status(201).json({ res.status(201).json({
success: true, success: true,
message: '출입 신청이 성공적으로 생성되었습니다.', message: '출입 신청이 성공적으로 생성되었습니다.',
data: { request_id: requestId } data: { request_id: requestId }
}); });
}); } catch (err) {
logger.error('출입 신청 생성 오류:', err);
res.status(500).json({ success: false, message: '출입 신청 생성 중 오류가 발생했습니다.' });
}
}; };
/** exports.getAllVisitRequests = async (req, res) => {
* 출입 신청 목록 조회 try {
*/ const filters = {
exports.getAllVisitRequests = (req, res) => { status: req.query.status,
const filters = { visit_date: req.query.visit_date,
status: req.query.status, start_date: req.query.start_date,
visit_date: req.query.visit_date, end_date: req.query.end_date,
start_date: req.query.start_date, requester_id: req.query.requester_id,
end_date: req.query.end_date, category_id: req.query.category_id
requester_id: req.query.requester_id, };
category_id: req.query.category_id
};
visitRequestModel.getAllVisitRequests(filters, (err, requests) => { const requests = await visitRequestModel.getAllVisitRequests(filters);
if (err) { res.json({ success: true, data: requests });
console.error('출입 신청 목록 조회 오류:', err); } catch (err) {
return res.status(500).json({ logger.error('출입 신청 목록 조회 오류:', err);
success: false, res.status(500).json({ success: false, message: '출입 신청 목록 조회 중 오류가 발생했습니다.' });
message: '출입 신청 목록 조회 중 오류가 발생했습니다.', }
error: err.message
});
}
res.json({
success: true,
data: requests
});
});
}; };
/** exports.getVisitRequestById = async (req, res) => {
* 출입 신청 상세 조회 try {
*/ const request = await visitRequestModel.getVisitRequestById(req.params.id);
exports.getVisitRequestById = (req, res) => {
const requestId = req.params.id;
visitRequestModel.getVisitRequestById(requestId, (err, request) => {
if (err) {
console.error('출입 신청 조회 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 조회 중 오류가 발생했습니다.',
error: err.message
});
}
if (!request) { if (!request) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
success: false,
message: '출입 신청을 찾을 수 없습니다.'
});
} }
res.json({ success: true, data: request });
res.json({ } catch (err) {
success: true, logger.error('출입 신청 조회 오류:', err);
data: request res.status(500).json({ success: false, message: '출입 신청 조회 중 오류가 발생했습니다.' });
}); }
});
}; };
/** exports.updateVisitRequest = async (req, res) => {
* 출입 신청 수정 try {
*/ const result = await visitRequestModel.updateVisitRequest(req.params.id, req.body);
exports.updateVisitRequest = (req, res) => {
const requestId = req.params.id;
const requestData = req.body;
visitRequestModel.updateVisitRequest(requestId, requestData, (err, result) => {
if (err) {
console.error('출입 신청 수정 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 수정 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
success: false,
message: '출입 신청을 찾을 수 없습니다.'
});
} }
res.json({ success: true, message: '출입 신청이 수정되었습니다.' });
res.json({ } catch (err) {
success: true, logger.error('출입 신청 수정 오류:', err);
message: '출입 신청 수정되었습니다.' res.status(500).json({ success: false, message: '출입 신청 수정 중 오류가 발생했습니다.' });
}); }
});
}; };
/** exports.deleteVisitRequest = async (req, res) => {
* 출입 신청 삭제 try {
*/ const result = await visitRequestModel.deleteVisitRequest(req.params.id);
exports.deleteVisitRequest = (req, res) => {
const requestId = req.params.id;
visitRequestModel.deleteVisitRequest(requestId, (err, result) => {
if (err) {
console.error('출입 신청 삭제 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 삭제 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
success: false,
message: '출입 신청을 찾을 수 없습니다.'
});
} }
res.json({ success: true, message: '출입 신청이 삭제되었습니다.' });
res.json({ } catch (err) {
success: true, logger.error('출입 신청 삭제 오류:', err);
message: '출입 신청 삭제되었습니다.' res.status(500).json({ success: false, message: '출입 신청 삭제 중 오류가 발생했습니다.' });
}); }
});
}; };
/** exports.approveVisitRequest = async (req, res) => {
* 출입 신청 승인 try {
*/ const result = await visitRequestModel.approveVisitRequest(req.params.id, req.user.user_id);
exports.approveVisitRequest = (req, res) => {
const requestId = req.params.id;
const approvedBy = req.user.user_id;
visitRequestModel.approveVisitRequest(requestId, approvedBy, (err, result) => {
if (err) {
console.error('출입 신청 승인 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 승인 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
success: false,
message: '출입 신청을 찾을 수 없습니다.'
});
} }
res.json({ success: true, message: '출입 신청이 승인되었습니다.' });
res.json({ } catch (err) {
success: true, logger.error('출입 신청 승인 오류:', err);
message: '출입 신청 승인되었습니다.' res.status(500).json({ success: false, message: '출입 신청 승인 중 오류가 발생했습니다.' });
}); }
});
}; };
/** exports.rejectVisitRequest = async (req, res) => {
* 출입 신청 반려 try {
*/ const rejectionData = {
exports.rejectVisitRequest = (req, res) => { approved_by: req.user.user_id,
const requestId = req.params.id; rejection_reason: req.body.rejection_reason || '사유 없음'
const approvedBy = req.user.user_id; };
const rejectionReason = req.body.rejection_reason || '사유 없음'; const result = await visitRequestModel.rejectVisitRequest(req.params.id, rejectionData);
const rejectionData = {
approved_by: approvedBy,
rejection_reason: rejectionReason
};
visitRequestModel.rejectVisitRequest(requestId, rejectionData, (err, result) => {
if (err) {
console.error('출입 신청 반려 오류:', err);
return res.status(500).json({
success: false,
message: '출입 신청 반려 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
success: false,
message: '출입 신청을 찾을 수 없습니다.'
});
} }
res.json({ success: true, message: '출입 신청이 반려되었습니다.' });
res.json({ } catch (err) {
success: true, logger.error('출입 신청 반려 오류:', err);
message: '출입 신청 반려되었습니다.' res.status(500).json({ success: false, message: '출입 신청 반려 중 오류가 발생했습니다.' });
}); }
});
}; };
// ==================== 방문 목적 관리 ==================== // ==================== 방문 목적 관리 ====================
/** exports.getAllVisitPurposes = async (req, res) => {
* 모든 방문 목적 조회 try {
*/ const purposes = await visitRequestModel.getAllVisitPurposes();
exports.getAllVisitPurposes = (req, res) => { res.json({ success: true, data: purposes });
visitRequestModel.getAllVisitPurposes((err, purposes) => { } catch (err) {
if (err) { logger.error('방문 목적 조회 오류:', err);
console.error('방문 목적 조회 오류:', err); res.status(500).json({ success: false, message: '방문 목적 조회 중 오류가 발생했습니다.' });
return res.status(500).json({
success: false,
message: '방문 목적 조회 중 오류가 발생했습니다.',
error: err.message
});
}
res.json({
success: true,
data: purposes
});
});
};
/**
* 활성 방문 목적만 조회
*/
exports.getActiveVisitPurposes = (req, res) => {
visitRequestModel.getActiveVisitPurposes((err, purposes) => {
if (err) {
console.error('활성 방문 목적 조회 오류:', err);
return res.status(500).json({
success: false,
message: '활성 방문 목적 조회 중 오류가 발생했습니다.',
error: err.message
});
}
res.json({
success: true,
data: purposes
});
});
};
/**
* 방문 목적 추가
*/
exports.createVisitPurpose = (req, res) => {
const purposeData = req.body;
if (!purposeData.purpose_name) {
return res.status(400).json({
success: false,
message: 'purpose_name은 필수 입력 항목입니다.'
});
} }
};
visitRequestModel.createVisitPurpose(purposeData, (err, purposeId) => { exports.getActiveVisitPurposes = async (req, res) => {
if (err) { try {
console.error('방문 목적 추가 오류:', err); const purposes = await visitRequestModel.getActiveVisitPurposes();
return res.status(500).json({ res.json({ success: true, data: purposes });
success: false, } catch (err) {
message: '방문 목적 추가 중 오류가 발생했습니다.', logger.error('활성 방문 목적 조회 오류:', err);
error: err.message res.status(500).json({ success: false, message: '활성 방문 목적 조회 중 오류가 발생했습니다.' });
}); }
};
exports.createVisitPurpose = async (req, res) => {
try {
if (!req.body.purpose_name) {
return res.status(400).json({ success: false, message: 'purpose_name은 필수 입력 항목입니다.' });
} }
const purposeId = await visitRequestModel.createVisitPurpose(req.body);
res.status(201).json({ res.status(201).json({
success: true, success: true,
message: '방문 목적이 추가되었습니다.', message: '방문 목적이 추가되었습니다.',
data: { purpose_id: purposeId } data: { purpose_id: purposeId }
}); });
}); } catch (err) {
logger.error('방문 목적 추가 오류:', err);
res.status(500).json({ success: false, message: '방문 목적 추가 중 오류가 발생했습니다.' });
}
}; };
/** exports.updateVisitPurpose = async (req, res) => {
* 방문 목적 수정 try {
*/ const result = await visitRequestModel.updateVisitPurpose(req.params.id, req.body);
exports.updateVisitPurpose = (req, res) => {
const purposeId = req.params.id;
const purposeData = req.body;
visitRequestModel.updateVisitPurpose(purposeId, purposeData, (err, result) => {
if (err) {
console.error('방문 목적 수정 오류:', err);
return res.status(500).json({
success: false,
message: '방문 목적 수정 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '방문 목적을 찾을 수 없습니다.' });
success: false,
message: '방문 목적을 찾을 수 없습니다.'
});
} }
res.json({ success: true, message: '방문 목적이 수정되었습니다.' });
res.json({ } catch (err) {
success: true, logger.error('방문 목적 수정 오류:', err);
message: '방문 목적 수정되었습니다.' res.status(500).json({ success: false, message: '방문 목적 수정 중 오류가 발생했습니다.' });
}); }
});
}; };
/** exports.deleteVisitPurpose = async (req, res) => {
* 방문 목적 삭제 try {
*/ const result = await visitRequestModel.deleteVisitPurpose(req.params.id);
exports.deleteVisitPurpose = (req, res) => {
const purposeId = req.params.id;
visitRequestModel.deleteVisitPurpose(purposeId, (err, result) => {
if (err) {
console.error('방문 목적 삭제 오류:', err);
return res.status(500).json({
success: false,
message: '방문 목적 삭제 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '방문 목적을 찾을 수 없습니다.' });
success: false,
message: '방문 목적을 찾을 수 없습니다.'
});
} }
res.json({ success: true, message: '방문 목적이 삭제되었습니다.' });
res.json({ } catch (err) {
success: true, logger.error('방문 목적 삭제 오류:', err);
message: '방문 목적 삭제되었습니다.' res.status(500).json({ success: false, message: '방문 목적 삭제 중 오류가 발생했습니다.' });
}); }
});
}; };
// ==================== 안전교육 기록 관리 ==================== // ==================== 안전교육 기록 관리 ====================
/** exports.createTrainingRecord = async (req, res) => {
* 안전교육 기록 생성 try {
*/ const trainingData = { trainer_id: req.user.user_id, ...req.body };
exports.createTrainingRecord = (req, res) => {
const trainerId = req.user.user_id;
const trainingData = {
trainer_id: trainerId,
...req.body
};
// 필수 필드 검증 const requiredFields = ['request_id', 'training_date', 'training_start_time'];
const requiredFields = ['request_id', 'training_date', 'training_start_time']; for (const field of requiredFields) {
for (const field of requiredFields) { if (!trainingData[field]) {
if (!trainingData[field]) { return res.status(400).json({ success: false, message: `${field}는 필수 입력 항목입니다.` });
return res.status(400).json({
success: false,
message: `${field}는 필수 입력 항목입니다.`
});
}
}
visitRequestModel.createTrainingRecord(trainingData, (err, trainingId) => {
if (err) {
console.error('안전교육 기록 생성 오류:', err);
return res.status(500).json({
success: false,
message: '안전교육 기록 생성 중 오류가 발생했습니다.',
error: err.message
});
}
// 안전교육 기록이 생성되면 출입 신청 상태를 training_completed로 변경
console.log(`[교육 완료] request_id=${trainingData.request_id} 상태를 training_completed로 변경 중...`);
visitRequestModel.updateVisitRequestStatus(trainingData.request_id, 'training_completed', (statusErr) => {
if (statusErr) {
console.error('출입 신청 상태 업데이트 오류:', statusErr);
// 에러가 발생해도 교육 기록은 생성되었으므로 성공 응답
} else {
console.log(`[교육 완료] request_id=${trainingData.request_id} 상태 변경 성공`);
} }
res.status(201).json({
success: true,
message: '안전교육 기록이 생성되었습니다.',
data: { training_id: trainingId }
});
});
});
};
/**
* 특정 출입 신청의 안전교육 기록 조회
*/
exports.getTrainingRecordByRequestId = (req, res) => {
const requestId = req.params.requestId;
visitRequestModel.getTrainingRecordByRequestId(requestId, (err, record) => {
if (err) {
console.error('안전교육 기록 조회 오류:', err);
return res.status(500).json({
success: false,
message: '안전교육 기록 조회 중 오류가 발생했습니다.',
error: err.message
});
} }
res.json({ const trainingId = await visitRequestModel.createTrainingRecord(trainingData);
// 안전교육 기록 생성 후 출입 신청 상태를 training_completed로 변경
try {
await visitRequestModel.updateVisitRequestStatus(trainingData.request_id, 'training_completed');
} catch (statusErr) {
logger.error('출입 신청 상태 업데이트 오류:', statusErr);
}
res.status(201).json({
success: true, success: true,
data: record || null message: '안전교육 기록이 생성되었습니다.',
}); data: { training_id: trainingId }
});
};
/**
* 안전교육 기록 수정
*/
exports.updateTrainingRecord = (req, res) => {
const trainingId = req.params.id;
const trainingData = req.body;
visitRequestModel.updateTrainingRecord(trainingId, trainingData, (err, result) => {
if (err) {
console.error('안전교육 기록 수정 오류:', err);
return res.status(500).json({
success: false,
message: '안전교육 기록 수정 중 오류가 발생했습니다.',
error: err.message
});
}
if (result.affectedRows === 0) {
return res.status(404).json({
success: false,
message: '안전교육 기록을 찾을 수 없습니다.'
});
}
res.json({
success: true,
message: '안전교육 기록이 수정되었습니다.'
});
});
};
/**
* 안전교육 완료 (서명 포함)
*/
exports.completeTraining = (req, res) => {
const trainingId = req.params.id;
const signatureData = req.body.signature_data;
if (!signatureData) {
return res.status(400).json({
success: false,
message: '서명 데이터가 필요합니다.'
}); });
} catch (err) {
logger.error('안전교육 기록 생성 오류:', err);
res.status(500).json({ success: false, message: '안전교육 기록 생성 중 오류가 발생했습니다.' });
} }
};
visitRequestModel.completeTraining(trainingId, signatureData, (err, result) => { exports.getTrainingRecordByRequestId = async (req, res) => {
if (err) { try {
console.error('안전교육 완료 처리 오류:', err); const record = await visitRequestModel.getTrainingRecordByRequestId(req.params.requestId);
return res.status(500).json({ res.json({ success: true, data: record || null });
success: false, } catch (err) {
message: '안전교육 완료 처리 중 오류가 발생했습니다.', logger.error('안전교육 기록 조회 오류:', err);
error: err.message res.status(500).json({ success: false, message: '안전교육 기록 조회 중 오류가 발생했습니다.' });
}); }
} };
exports.updateTrainingRecord = async (req, res) => {
try {
const result = await visitRequestModel.updateTrainingRecord(req.params.id, req.body);
if (result.affectedRows === 0) { if (result.affectedRows === 0) {
return res.status(404).json({ return res.status(404).json({ success: false, message: '안전교육 기록을 찾을 수 없습니다.' });
success: false, }
message: '안전교육 기록을 찾을 수 없습니다.' res.json({ success: true, message: '안전교육 기록이 수정되었습니다.' });
}); } catch (err) {
logger.error('안전교육 기록 수정 오류:', err);
res.status(500).json({ success: false, message: '안전교육 기록 수정 중 오류가 발생했습니다.' });
}
};
exports.completeTraining = async (req, res) => {
try {
const trainingId = req.params.id;
const signatureData = req.body.signature_data;
if (!signatureData) {
return res.status(400).json({ success: false, message: '서명 데이터가 필요합니다.' });
} }
// 교육 완료 후 출입 신청 상태를 'training_completed'로 변경 const result = await visitRequestModel.completeTraining(trainingId, signatureData);
visitRequestModel.getTrainingRecordByRequestId(trainingId, (err, record) => { if (result.affectedRows === 0) {
if (err || !record) { return res.status(404).json({ success: false, message: '안전교육 기록을 찾을 수 없습니다.' });
return res.json({ }
success: true,
message: '안전교육이 완료되었습니다.' // 교육 완료 후 출입 신청 상태 변경
}); try {
const record = await visitRequestModel.getTrainingRecordByRequestId(trainingId);
if (record) {
await visitRequestModel.updateVisitRequestStatus(record.request_id, 'training_completed');
} }
} catch (statusErr) {
visitRequestModel.updateVisitRequestStatus(record.request_id, 'training_completed', (err) => { logger.error('출입 신청 상태 업데이트 오류:', statusErr);
if (err) {
console.error('출입 신청 상태 업데이트 오류:', err);
}
res.json({
success: true,
message: '안전교육이 완료되었습니다.'
});
});
});
});
};
/**
* 안전교육 기록 목록 조회
*/
exports.getTrainingRecords = (req, res) => {
const filters = {
training_date: req.query.training_date,
start_date: req.query.start_date,
end_date: req.query.end_date,
trainer_id: req.query.trainer_id
};
visitRequestModel.getTrainingRecords(filters, (err, records) => {
if (err) {
console.error('안전교육 기록 목록 조회 오류:', err);
return res.status(500).json({
success: false,
message: '안전교육 기록 목록 조회 중 오류가 발생했습니다.',
error: err.message
});
} }
res.json({ res.json({ success: true, message: '안전교육이 완료되었습니다.' });
success: true, } catch (err) {
data: records logger.error('안전교육 완료 처리 오류:', err);
}); res.status(500).json({ success: false, message: '안전교육 완료 처리 중 오류가 발생했습니다.' });
}); }
};
exports.getTrainingRecords = async (req, res) => {
try {
const filters = {
training_date: req.query.training_date,
start_date: req.query.start_date,
end_date: req.query.end_date,
trainer_id: req.query.trainer_id
};
const records = await visitRequestModel.getTrainingRecords(filters);
res.json({ success: true, data: records });
} catch (err) {
logger.error('안전교육 기록 목록 조회 오류:', err);
res.status(500).json({ success: false, message: '안전교육 기록 목록 조회 중 오류가 발생했습니다.' });
}
}; };

View File

@@ -4,198 +4,149 @@
const workIssueModel = require('../models/workIssueModel'); const workIssueModel = require('../models/workIssueModel');
const imageUploadService = require('../services/imageUploadService'); const imageUploadService = require('../services/imageUploadService');
const logger = require('../utils/logger');
// ==================== 신고 카테고리 관리 ==================== // ==================== 신고 카테고리 관리 ====================
/** exports.getAllCategories = async (req, res) => {
* 모든 카테고리 조회 try {
*/ const categories = await workIssueModel.getAllCategories();
exports.getAllCategories = (req, res) => {
workIssueModel.getAllCategories((err, categories) => {
if (err) {
console.error('카테고리 조회 실패:', err);
return res.status(500).json({ success: false, error: '카테고리 조회 실패' });
}
res.json({ success: true, data: categories }); res.json({ success: true, data: categories });
}); } catch (err) {
logger.error('카테고리 조회 실패:', err);
res.status(500).json({ success: false, error: '카테고리 조회 실패' });
}
}; };
/** exports.getCategoriesByType = async (req, res) => {
* 타입별 카테고리 조회 try {
*/ const { type } = req.params;
exports.getCategoriesByType = (req, res) => {
const { type } = req.params;
if (!['nonconformity', 'safety', 'facility'].includes(type)) { if (!['nonconformity', 'safety', 'facility'].includes(type)) {
return res.status(400).json({ success: false, error: '유효하지 않은 카테고리 타입입니다.' }); return res.status(400).json({ success: false, error: '유효하지 않은 카테고리 타입입니다.' });
}
workIssueModel.getCategoriesByType(type, (err, categories) => {
if (err) {
console.error('카테고리 조회 실패:', err);
return res.status(500).json({ success: false, error: '카테고리 조회 실패' });
} }
const categories = await workIssueModel.getCategoriesByType(type);
res.json({ success: true, data: categories }); res.json({ success: true, data: categories });
}); } catch (err) {
}; logger.error('카테고리 조회 실패:', err);
res.status(500).json({ success: false, error: '카테고리 조회 실패' });
/**
* 카테고리 생성
*/
exports.createCategory = (req, res) => {
const { category_type, category_name, description, display_order } = req.body;
if (!category_type || !category_name) {
return res.status(400).json({ success: false, error: '카테고리 타입과 이름은 필수입니다.' });
} }
workIssueModel.createCategory(
{ category_type, category_name, description, display_order },
(err, categoryId) => {
if (err) {
console.error('카테고리 생성 실패:', err);
return res.status(500).json({ success: false, error: '카테고리 생성 실패' });
}
res.status(201).json({
success: true,
message: '카테고리가 생성되었습니다.',
data: { category_id: categoryId }
});
}
);
}; };
/** exports.createCategory = async (req, res) => {
* 카테고리 수정 try {
*/ const { category_type, category_name, description, display_order } = req.body;
exports.updateCategory = (req, res) => {
const { id } = req.params;
const { category_name, description, display_order, is_active } = req.body;
workIssueModel.updateCategory( if (!category_type || !category_name) {
id, return res.status(400).json({ success: false, error: '카테고리 타입과 이름은 필수입니다.' });
{ category_name, description, display_order, is_active },
(err, result) => {
if (err) {
console.error('카테고리 수정 실패:', err);
return res.status(500).json({ success: false, error: '카테고리 수정 실패' });
}
res.json({ success: true, message: '카테고리가 수정되었습니다.' });
} }
);
const categoryId = await workIssueModel.createCategory({ category_type, category_name, description, display_order });
res.status(201).json({
success: true,
message: '카테고리가 생성되었습니다.',
data: { category_id: categoryId }
});
} catch (err) {
logger.error('카테고리 생성 실패:', err);
res.status(500).json({ success: false, error: '카테고리 생성 실패' });
}
}; };
/** exports.updateCategory = async (req, res) => {
* 카테고리 삭제 try {
*/ const { id } = req.params;
exports.deleteCategory = (req, res) => { const { category_name, description, display_order, is_active } = req.body;
const { id } = req.params;
workIssueModel.deleteCategory(id, (err, result) => { await workIssueModel.updateCategory(id, { category_name, description, display_order, is_active });
if (err) { res.json({ success: true, message: '카테고리가 수정되었습니다.' });
console.error('카테고리 삭제 실패:', err); } catch (err) {
return res.status(500).json({ success: false, error: '카테고리 삭제 실패' }); logger.error('카테고리 수정 실패:', err);
} res.status(500).json({ success: false, error: '카테고리 수정 실패' });
}
};
exports.deleteCategory = async (req, res) => {
try {
const { id } = req.params;
await workIssueModel.deleteCategory(id);
res.json({ success: true, message: '카테고리가 삭제되었습니다.' }); res.json({ success: true, message: '카테고리가 삭제되었습니다.' });
}); } catch (err) {
logger.error('카테고리 삭제 실패:', err);
res.status(500).json({ success: false, error: '카테고리 삭제 실패' });
}
}; };
// ==================== 사전 정의 항목 관리 ==================== // ==================== 사전 정의 항목 관리 ====================
/** exports.getItemsByCategory = async (req, res) => {
* 카테고리별 항목 조회 try {
*/ const { categoryId } = req.params;
exports.getItemsByCategory = (req, res) => { const items = await workIssueModel.getItemsByCategory(categoryId);
const { categoryId } = req.params;
workIssueModel.getItemsByCategory(categoryId, (err, items) => {
if (err) {
console.error('항목 조회 실패:', err);
return res.status(500).json({ success: false, error: '항목 조회 실패' });
}
res.json({ success: true, data: items }); res.json({ success: true, data: items });
}); } catch (err) {
}; logger.error('항목 조회 실패:', err);
res.status(500).json({ success: false, error: '항목 조회 실패' });
/**
* 모든 항목 조회
*/
exports.getAllItems = (req, res) => {
workIssueModel.getAllItems((err, items) => {
if (err) {
console.error('항목 조회 실패:', err);
return res.status(500).json({ success: false, error: '항목 조회 실패' });
}
res.json({ success: true, data: items });
});
};
/**
* 항목 생성
*/
exports.createItem = (req, res) => {
const { category_id, item_name, description, severity, display_order } = req.body;
if (!category_id || !item_name) {
return res.status(400).json({ success: false, error: '카테고리 ID와 항목명은 필수입니다.' });
} }
workIssueModel.createItem(
{ category_id, item_name, description, severity, display_order },
(err, itemId) => {
if (err) {
console.error('항목 생성 실패:', err);
return res.status(500).json({ success: false, error: '항목 생성 실패' });
}
res.status(201).json({
success: true,
message: '항목이 생성되었습니다.',
data: { item_id: itemId }
});
}
);
}; };
/** exports.getAllItems = async (req, res) => {
* 항목 수정 try {
*/ const items = await workIssueModel.getAllItems();
exports.updateItem = (req, res) => { res.json({ success: true, data: items });
const { id } = req.params; } catch (err) {
const { item_name, description, severity, display_order, is_active } = req.body; logger.error('항목 조회 실패:', err);
res.status(500).json({ success: false, error: '항목 조회 실패' });
workIssueModel.updateItem( }
id,
{ item_name, description, severity, display_order, is_active },
(err, result) => {
if (err) {
console.error('항목 수정 실패:', err);
return res.status(500).json({ success: false, error: '항목 수정 실패' });
}
res.json({ success: true, message: '항목이 수정되었습니다.' });
}
);
}; };
/** exports.createItem = async (req, res) => {
* 항목 삭제 try {
*/ const { category_id, item_name, description, severity, display_order } = req.body;
exports.deleteItem = (req, res) => {
const { id } = req.params;
workIssueModel.deleteItem(id, (err, result) => { if (!category_id || !item_name) {
if (err) { return res.status(400).json({ success: false, error: '카테고리 ID와 항목명은 필수입니다.' });
console.error('항목 삭제 실패:', err);
return res.status(500).json({ success: false, error: '항목 삭제 실패' });
} }
const itemId = await workIssueModel.createItem({ category_id, item_name, description, severity, display_order });
res.status(201).json({
success: true,
message: '항목이 생성되었습니다.',
data: { item_id: itemId }
});
} catch (err) {
logger.error('항목 생성 실패:', err);
res.status(500).json({ success: false, error: '항목 생성 실패' });
}
};
exports.updateItem = async (req, res) => {
try {
const { id } = req.params;
const { item_name, description, severity, display_order, is_active } = req.body;
await workIssueModel.updateItem(id, { item_name, description, severity, display_order, is_active });
res.json({ success: true, message: '항목이 수정되었습니다.' });
} catch (err) {
logger.error('항목 수정 실패:', err);
res.status(500).json({ success: false, error: '항목 수정 실패' });
}
};
exports.deleteItem = async (req, res) => {
try {
const { id } = req.params;
await workIssueModel.deleteItem(id);
res.json({ success: true, message: '항목이 삭제되었습니다.' }); res.json({ success: true, message: '항목이 삭제되었습니다.' });
}); } catch (err) {
logger.error('항목 삭제 실패:', err);
res.status(500).json({ success: false, error: '항목 삭제 실패' });
}
}; };
// ==================== 문제 신고 관리 ==================== // ==================== 문제 신고 관리 ====================
/**
* 신고 생성
*/
exports.createReport = async (req, res) => { exports.createReport = async (req, res) => {
try { try {
const { const {
@@ -206,7 +157,7 @@ exports.createReport = async (req, res) => {
visit_request_id, visit_request_id,
issue_category_id, issue_category_id,
issue_item_id, issue_item_id,
custom_item_name, // 직접 입력한 항목명 custom_item_name,
additional_description, additional_description,
photos = [] photos = []
} = req.body; } = req.body;
@@ -217,42 +168,25 @@ exports.createReport = async (req, res) => {
return res.status(400).json({ success: false, error: '신고 카테고리는 필수입니다.' }); return res.status(400).json({ success: false, error: '신고 카테고리는 필수입니다.' });
} }
// 위치 정보 검증 (지도 선택 또는 기타 위치)
if (!factory_category_id && !custom_location) { if (!factory_category_id && !custom_location) {
return res.status(400).json({ success: false, error: '위치 정보는 필수입니다.' }); return res.status(400).json({ success: false, error: '위치 정보는 필수입니다.' });
} }
// 항목 검증 (기존 항목 또는 직접 입력)
if (!issue_item_id && !custom_item_name) { if (!issue_item_id && !custom_item_name) {
return res.status(400).json({ success: false, error: '신고 항목은 필수입니다.' }); return res.status(400).json({ success: false, error: '신고 항목은 필수입니다.' });
} }
// 직접 입력한 항목이 있으면 DB에 저장
let finalItemId = issue_item_id; let finalItemId = issue_item_id;
if (custom_item_name && !issue_item_id) { if (custom_item_name && !issue_item_id) {
try { finalItemId = await workIssueModel.createItem({
finalItemId = await new Promise((resolve, reject) => { category_id: issue_category_id,
workIssueModel.createItem( item_name: custom_item_name,
{ description: '사용자 직접 입력',
category_id: issue_category_id, severity: 'medium',
item_name: custom_item_name, display_order: 999
description: '사용자 직접 입력', });
severity: 'medium',
display_order: 999 // 마지막에 표시
},
(err, itemId) => {
if (err) reject(err);
else resolve(itemId);
}
);
});
} catch (itemErr) {
console.error('커스텀 항목 생성 실패:', itemErr);
return res.status(500).json({ success: false, error: '항목 저장 실패' });
}
} }
// 사진 저장 (최대 5장)
const photoPaths = { const photoPaths = {
photo_path1: null, photo_path1: null,
photo_path2: null, photo_path2: null,
@@ -283,73 +217,56 @@ exports.createReport = async (req, res) => {
...photoPaths ...photoPaths
}; };
workIssueModel.createReport(reportData, (err, reportId) => { const reportId = await workIssueModel.createReport(reportData);
if (err) { res.status(201).json({
console.error('신고 생성 실패:', err); success: true,
return res.status(500).json({ success: false, error: '신고 생성 실패' }); message: '문제 신고가 등록되었습니다.',
} data: { report_id: reportId }
res.status(201).json({
success: true,
message: '문제 신고가 등록되었습니다.',
data: { report_id: reportId }
});
}); });
} catch (error) { } catch (err) {
console.error('신고 생성 에러:', error); logger.error('신고 생성 실패:', err);
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' }); res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
} }
}; };
/** exports.getAllReports = async (req, res) => {
* 신고 목록 조회 try {
*/ const filters = {
exports.getAllReports = (req, res) => { status: req.query.status,
const filters = { category_type: req.query.category_type,
status: req.query.status, issue_category_id: req.query.issue_category_id,
category_type: req.query.category_type, factory_category_id: req.query.factory_category_id,
issue_category_id: req.query.issue_category_id, workplace_id: req.query.workplace_id,
factory_category_id: req.query.factory_category_id, assigned_user_id: req.query.assigned_user_id,
workplace_id: req.query.workplace_id, start_date: req.query.start_date,
assigned_user_id: req.query.assigned_user_id, end_date: req.query.end_date,
start_date: req.query.start_date, search: req.query.search,
end_date: req.query.end_date, limit: req.query.limit,
search: req.query.search, offset: req.query.offset
limit: req.query.limit, };
offset: req.query.offset
};
// 일반 사용자는 자신의 신고만 조회 (관리자 제외) const userLevel = req.user.access_level;
const userLevel = req.user.access_level; if (!['admin', 'system', 'support_team'].includes(userLevel)) {
if (!['admin', 'system', 'support_team'].includes(userLevel)) { filters.reporter_id = req.user.user_id;
filters.reporter_id = req.user.user_id;
}
workIssueModel.getAllReports(filters, (err, reports) => {
if (err) {
console.error('신고 목록 조회 실패:', err);
return res.status(500).json({ success: false, error: '신고 목록 조회 실패' });
} }
const reports = await workIssueModel.getAllReports(filters);
res.json({ success: true, data: reports }); res.json({ success: true, data: reports });
}); } catch (err) {
logger.error('신고 목록 조회 실패:', err);
res.status(500).json({ success: false, error: '신고 목록 조회 실패' });
}
}; };
/** exports.getReportById = async (req, res) => {
* 신고 상세 조회 try {
*/ const { id } = req.params;
exports.getReportById = (req, res) => { const report = await workIssueModel.getReportById(id);
const { id } = req.params;
workIssueModel.getReportById(id, (err, report) => {
if (err) {
console.error('신고 상세 조회 실패:', err);
return res.status(500).json({ success: false, error: '신고 상세 조회 실패' });
}
if (!report) { if (!report) {
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' }); return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
} }
// 권한 확인: 본인, 담당자, 또는 관리자
const userLevel = req.user.access_level; const userLevel = req.user.access_level;
const isOwner = report.reporter_id === req.user.user_id; const isOwner = report.reporter_id === req.user.user_id;
const isAssignee = report.assigned_user_id === req.user.user_id; const isAssignee = report.assigned_user_id === req.user.user_id;
@@ -360,109 +277,84 @@ exports.getReportById = (req, res) => {
} }
res.json({ success: true, data: report }); res.json({ success: true, data: report });
}); } catch (err) {
logger.error('신고 상세 조회 실패:', err);
res.status(500).json({ success: false, error: '신고 상세 조회 실패' });
}
}; };
/**
* 신고 수정
*/
exports.updateReport = async (req, res) => { exports.updateReport = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
// 기존 신고 확인 const report = await workIssueModel.getReportById(id);
workIssueModel.getReportById(id, async (err, report) => { if (!report) {
if (err) { return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
console.error('신고 조회 실패:', err); }
return res.status(500).json({ success: false, error: '신고 조회 실패' });
} const userLevel = req.user.access_level;
const isOwner = report.reporter_id === req.user.user_id;
if (!report) { const isManager = ['admin', 'system'].includes(userLevel);
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
} if (!isOwner && !isManager) {
return res.status(403).json({ success: false, error: '수정 권한이 없습니다.' });
// 권한 확인 }
const userLevel = req.user.access_level;
const isOwner = report.reporter_id === req.user.user_id; if (!isManager && report.status !== 'reported') {
const isManager = ['admin', 'system'].includes(userLevel); return res.status(400).json({ success: false, error: '이미 접수된 신고는 수정할 수 없습니다.' });
}
if (!isOwner && !isManager) {
return res.status(403).json({ success: false, error: '수정 권한이 없습니다.' }); const {
} factory_category_id,
workplace_id,
// 상태 확인: reported 상태에서만 수정 가능 (관리자 제외) custom_location,
if (!isManager && report.status !== 'reported') { issue_category_id,
return res.status(400).json({ success: false, error: '이미 접수된 신고는 수정할 수 없습니다.' }); issue_item_id,
} additional_description,
photos = []
const { } = req.body;
factory_category_id,
workplace_id, const photoPaths = {};
custom_location, for (let i = 0; i < Math.min(photos.length, 5); i++) {
issue_category_id, if (photos[i]) {
issue_item_id, const oldPath = report[`photo_path${i + 1}`];
additional_description, if (oldPath) {
photos = [] await imageUploadService.deleteFile(oldPath);
} = req.body; }
const savedPath = await imageUploadService.saveBase64Image(photos[i], 'issue');
// 사진 업데이트 처리 if (savedPath) {
const photoPaths = {}; photoPaths[`photo_path${i + 1}`] = savedPath;
for (let i = 0; i < Math.min(photos.length, 5); i++) { }
if (photos[i]) { }
// 기존 사진 삭제 }
const oldPath = report[`photo_path${i + 1}`];
if (oldPath) { const updateData = {
await imageUploadService.deleteFile(oldPath); factory_category_id,
} workplace_id,
// 새 사진 저장 custom_location,
const savedPath = await imageUploadService.saveBase64Image(photos[i], 'issue'); issue_category_id,
if (savedPath) { issue_item_id,
photoPaths[`photo_path${i + 1}`] = savedPath; additional_description,
} ...photoPaths
} };
}
await workIssueModel.updateReport(id, updateData, req.user.user_id);
const updateData = { res.json({ success: true, message: '신고가 수정되었습니다.' });
factory_category_id, } catch (err) {
workplace_id, logger.error('신고 수정 실패:', err);
custom_location, res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
issue_category_id, }
issue_item_id, };
additional_description,
...photoPaths exports.deleteReport = async (req, res) => {
}; try {
const { id } = req.params;
workIssueModel.updateReport(id, updateData, req.user.user_id, (updateErr, result) => {
if (updateErr) { const report = await workIssueModel.getReportById(id);
console.error('신고 수정 실패:', updateErr);
return res.status(500).json({ success: false, error: '신고 수정 실패' });
}
res.json({ success: true, message: '신고가 수정되었습니다.' });
});
});
} catch (error) {
console.error('신고 수정 에러:', error);
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
}
};
/**
* 신고 삭제
*/
exports.deleteReport = async (req, res) => {
const { id } = req.params;
workIssueModel.getReportById(id, async (err, report) => {
if (err) {
console.error('신고 조회 실패:', err);
return res.status(500).json({ success: false, error: '신고 조회 실패' });
}
if (!report) { if (!report) {
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' }); return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
} }
// 권한 확인
const userLevel = req.user.access_level; const userLevel = req.user.access_level;
const isOwner = report.reporter_id === req.user.user_id; const isOwner = report.reporter_id === req.user.user_id;
const isManager = ['admin', 'system'].includes(userLevel); const isManager = ['admin', 'system'].includes(userLevel);
@@ -471,92 +363,74 @@ exports.deleteReport = async (req, res) => {
return res.status(403).json({ success: false, error: '삭제 권한이 없습니다.' }); return res.status(403).json({ success: false, error: '삭제 권한이 없습니다.' });
} }
workIssueModel.deleteReport(id, async (deleteErr, { result, photos }) => { const { photos } = await workIssueModel.deleteReport(id);
if (deleteErr) {
console.error('신고 삭제 실패:', deleteErr);
return res.status(500).json({ success: false, error: '신고 삭제 실패' });
}
// 사진 파일 삭제 if (photos) {
if (photos) { const allPhotos = [
const allPhotos = [ photos.photo_path1, photos.photo_path2, photos.photo_path3,
photos.photo_path1, photos.photo_path2, photos.photo_path3, photos.photo_path4, photos.photo_path5,
photos.photo_path4, photos.photo_path5, photos.resolution_photo_path1, photos.resolution_photo_path2
photos.resolution_photo_path1, photos.resolution_photo_path2 ].filter(Boolean);
].filter(Boolean); await imageUploadService.deleteMultipleFiles(allPhotos);
await imageUploadService.deleteMultipleFiles(allPhotos); }
}
res.json({ success: true, message: '신고가 삭제되었습니다.' }); res.json({ success: true, message: '신고가 삭제되었습니다.' });
}); } catch (err) {
}); logger.error('신고 삭제 실패:', err);
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
}
}; };
// ==================== 상태 관리 ==================== // ==================== 상태 관리 ====================
/** exports.receiveReport = async (req, res) => {
* 신고 접수 try {
*/ const { id } = req.params;
exports.receiveReport = (req, res) => { await workIssueModel.receiveReport(id, req.user.user_id);
const { id } = req.params;
workIssueModel.receiveReport(id, req.user.user_id, (err, result) => {
if (err) {
console.error('신고 접수 실패:', err);
return res.status(400).json({ success: false, error: err.message || '신고 접수 실패' });
}
res.json({ success: true, message: '신고가 접수되었습니다.' }); res.json({ success: true, message: '신고가 접수되었습니다.' });
}); } catch (err) {
}; logger.error('신고 접수 실패:', err);
res.status(400).json({ success: false, error: err.message || '신고 접수 실패' });
/**
* 담당자 배정
*/
exports.assignReport = (req, res) => {
const { id } = req.params;
const { assigned_department, assigned_user_id } = req.body;
if (!assigned_user_id) {
return res.status(400).json({ success: false, error: '담당자는 필수입니다.' });
} }
};
workIssueModel.assignReport(id, { exports.assignReport = async (req, res) => {
assigned_department, try {
assigned_user_id, const { id } = req.params;
assigned_by: req.user.user_id const { assigned_department, assigned_user_id } = req.body;
}, (err, result) => {
if (err) { if (!assigned_user_id) {
console.error('담당자 배정 실패:', err); return res.status(400).json({ success: false, error: '담당자는 필수입니다.' });
return res.status(400).json({ success: false, error: err.message || '담당자 배정 실패' });
} }
await workIssueModel.assignReport(id, {
assigned_department,
assigned_user_id,
assigned_by: req.user.user_id
});
res.json({ success: true, message: '담당자가 배정되었습니다.' }); res.json({ success: true, message: '담당자가 배정되었습니다.' });
}); } catch (err) {
logger.error('담당자 배정 실패:', err);
res.status(400).json({ success: false, error: err.message || '담당자 배정 실패' });
}
}; };
/** exports.startProcessing = async (req, res) => {
* 처리 시작 try {
*/ const { id } = req.params;
exports.startProcessing = (req, res) => { await workIssueModel.startProcessing(id, req.user.user_id);
const { id } = req.params;
workIssueModel.startProcessing(id, req.user.user_id, (err, result) => {
if (err) {
console.error('처리 시작 실패:', err);
return res.status(400).json({ success: false, error: err.message || '처리 시작 실패' });
}
res.json({ success: true, message: '처리가 시작되었습니다.' }); res.json({ success: true, message: '처리가 시작되었습니다.' });
}); } catch (err) {
logger.error('처리 시작 실패:', err);
res.status(400).json({ success: false, error: err.message || '처리 시작 실패' });
}
}; };
/**
* 처리 완료
*/
exports.completeReport = async (req, res) => { exports.completeReport = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const { resolution_notes, resolution_photos = [] } = req.body; const { resolution_notes, resolution_photos = [] } = req.body;
// 완료 사진 저장
let resolution_photo_path1 = null; let resolution_photo_path1 = null;
let resolution_photo_path2 = null; let resolution_photo_path2 = null;
@@ -567,108 +441,83 @@ exports.completeReport = async (req, res) => {
resolution_photo_path2 = await imageUploadService.saveBase64Image(resolution_photos[1], 'resolution'); resolution_photo_path2 = await imageUploadService.saveBase64Image(resolution_photos[1], 'resolution');
} }
workIssueModel.completeReport(id, { await workIssueModel.completeReport(id, {
resolution_notes, resolution_notes,
resolution_photo_path1, resolution_photo_path1,
resolution_photo_path2, resolution_photo_path2,
resolved_by: req.user.user_id resolved_by: req.user.user_id
}, (err, result) => {
if (err) {
console.error('처리 완료 실패:', err);
return res.status(400).json({ success: false, error: err.message || '처리 완료 실패' });
}
res.json({ success: true, message: '처리가 완료되었습니다.' });
}); });
} catch (error) { res.json({ success: true, message: '처리가 완료되었습니다.' });
console.error('처리 완료 에러:', error); } catch (err) {
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' }); logger.error('처리 완료 실패:', err);
res.status(400).json({ success: false, error: err.message || '처리 완료 실패' });
} }
}; };
/** exports.closeReport = async (req, res) => {
* 신고 종료 try {
*/ const { id } = req.params;
exports.closeReport = (req, res) => { await workIssueModel.closeReport(id, req.user.user_id);
const { id } = req.params;
workIssueModel.closeReport(id, req.user.user_id, (err, result) => {
if (err) {
console.error('신고 종료 실패:', err);
return res.status(400).json({ success: false, error: err.message || '신고 종료 실패' });
}
res.json({ success: true, message: '신고가 종료되었습니다.' }); res.json({ success: true, message: '신고가 종료되었습니다.' });
}); } catch (err) {
logger.error('신고 종료 실패:', err);
res.status(400).json({ success: false, error: err.message || '신고 종료 실패' });
}
}; };
/** exports.getStatusLogs = async (req, res) => {
* 상태 변경 이력 조회 try {
*/ const { id } = req.params;
exports.getStatusLogs = (req, res) => { const logs = await workIssueModel.getStatusLogs(id);
const { id } = req.params;
workIssueModel.getStatusLogs(id, (err, logs) => {
if (err) {
console.error('상태 이력 조회 실패:', err);
return res.status(500).json({ success: false, error: '상태 이력 조회 실패' });
}
res.json({ success: true, data: logs }); res.json({ success: true, data: logs });
}); } catch (err) {
logger.error('상태 이력 조회 실패:', err);
res.status(500).json({ success: false, error: '상태 이력 조회 실패' });
}
}; };
// ==================== 통계 ==================== // ==================== 통계 ====================
/** exports.getStatsSummary = async (req, res) => {
* 통계 요약 try {
*/ const filters = {
exports.getStatsSummary = (req, res) => { start_date: req.query.start_date,
const filters = { end_date: req.query.end_date,
start_date: req.query.start_date, factory_category_id: req.query.factory_category_id
end_date: req.query.end_date, };
factory_category_id: req.query.factory_category_id const stats = await workIssueModel.getStatsSummary(filters);
};
workIssueModel.getStatsSummary(filters, (err, stats) => {
if (err) {
console.error('통계 조회 실패:', err);
return res.status(500).json({ success: false, error: '통계 조회 실패' });
}
res.json({ success: true, data: stats }); res.json({ success: true, data: stats });
}); } catch (err) {
logger.error('통계 조회 실패:', err);
res.status(500).json({ success: false, error: '통계 조회 실패' });
}
}; };
/** exports.getStatsByCategory = async (req, res) => {
* 카테고리별 통계 try {
*/ const filters = {
exports.getStatsByCategory = (req, res) => { start_date: req.query.start_date,
const filters = { end_date: req.query.end_date
start_date: req.query.start_date, };
end_date: req.query.end_date const stats = await workIssueModel.getStatsByCategory(filters);
};
workIssueModel.getStatsByCategory(filters, (err, stats) => {
if (err) {
console.error('카테고리별 통계 조회 실패:', err);
return res.status(500).json({ success: false, error: '통계 조회 실패' });
}
res.json({ success: true, data: stats }); res.json({ success: true, data: stats });
}); } catch (err) {
logger.error('카테고리별 통계 조회 실패:', err);
res.status(500).json({ success: false, error: '통계 조회 실패' });
}
}; };
/** exports.getStatsByWorkplace = async (req, res) => {
* 작업장별 통계 try {
*/ const filters = {
exports.getStatsByWorkplace = (req, res) => { start_date: req.query.start_date,
const filters = { end_date: req.query.end_date,
start_date: req.query.start_date, factory_category_id: req.query.factory_category_id
end_date: req.query.end_date, };
factory_category_id: req.query.factory_category_id const stats = await workIssueModel.getStatsByWorkplace(filters);
};
workIssueModel.getStatsByWorkplace(filters, (err, stats) => {
if (err) {
console.error('작업장별 통계 조회 실패:', err);
return res.status(500).json({ success: false, error: '통계 조회 실패' });
}
res.json({ success: true, data: stats }); res.json({ success: true, data: stats });
}); } catch (err) {
logger.error('작업장별 통계 조회 실패:', err);
res.status(500).json({ success: false, error: '통계 조회 실패' });
}
}; };

View File

@@ -8,15 +8,12 @@
*/ */
const workplaceModel = require('../models/workplaceModel'); const workplaceModel = require('../models/workplaceModel');
const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors'); const { ValidationError, NotFoundError } = require('../utils/errors');
const { asyncHandler } = require('../middlewares/errorHandler'); const { asyncHandler } = require('../middlewares/errorHandler');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
// ==================== 카테고리(공장) 관련 ==================== // ==================== 카테고리(공장) 관련 ====================
/**
* 카테고리 생성
*/
exports.createCategory = asyncHandler(async (req, res) => { exports.createCategory = asyncHandler(async (req, res) => {
const categoryData = req.body; const categoryData = req.body;
@@ -26,12 +23,7 @@ exports.createCategory = asyncHandler(async (req, res) => {
logger.info('카테고리 생성 요청', { name: categoryData.category_name }); logger.info('카테고리 생성 요청', { name: categoryData.category_name });
const id = await new Promise((resolve, reject) => { const id = await workplaceModel.createCategory(categoryData);
workplaceModel.createCategory(categoryData, (err, lastID) => {
if (err) reject(new DatabaseError('카테고리 생성 중 오류가 발생했습니다'));
else resolve(lastID);
});
});
logger.info('카테고리 생성 성공', { category_id: id }); logger.info('카테고리 생성 성공', { category_id: id });
@@ -42,16 +34,8 @@ exports.createCategory = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 전체 카테고리 조회
*/
exports.getAllCategories = asyncHandler(async (req, res) => { exports.getAllCategories = asyncHandler(async (req, res) => {
const rows = await new Promise((resolve, reject) => { const rows = await workplaceModel.getAllCategories();
workplaceModel.getAllCategories((err, data) => {
if (err) reject(new DatabaseError('카테고리 목록 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -60,16 +44,8 @@ exports.getAllCategories = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 활성 카테고리만 조회
*/
exports.getActiveCategories = asyncHandler(async (req, res) => { exports.getActiveCategories = asyncHandler(async (req, res) => {
const rows = await new Promise((resolve, reject) => { const rows = await workplaceModel.getActiveCategories();
workplaceModel.getActiveCategories((err, data) => {
if (err) reject(new DatabaseError('활성 카테고리 목록 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -78,18 +54,9 @@ exports.getActiveCategories = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 단일 카테고리 조회
*/
exports.getCategoryById = asyncHandler(async (req, res) => { exports.getCategoryById = asyncHandler(async (req, res) => {
const categoryId = req.params.id; const categoryId = req.params.id;
const category = await workplaceModel.getCategoryById(categoryId);
const category = await new Promise((resolve, reject) => {
workplaceModel.getCategoryById(categoryId, (err, data) => {
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!category) { if (!category) {
throw new NotFoundError('카테고리를 찾을 수 없습니다'); throw new NotFoundError('카테고리를 찾을 수 없습니다');
@@ -102,9 +69,6 @@ exports.getCategoryById = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 카테고리 수정
*/
exports.updateCategory = asyncHandler(async (req, res) => { exports.updateCategory = asyncHandler(async (req, res) => {
const categoryId = req.params.id; const categoryId = req.params.id;
const categoryData = req.body; const categoryData = req.body;
@@ -115,19 +79,11 @@ exports.updateCategory = asyncHandler(async (req, res) => {
logger.info('카테고리 수정 요청', { category_id: categoryId }); logger.info('카테고리 수정 요청', { category_id: categoryId });
// 기존 카테고리 정보 가져오기 const existingCategory = await workplaceModel.getCategoryById(categoryId);
const existingCategory = await new Promise((resolve, reject) => {
workplaceModel.getCategoryById(categoryId, (err, data) => {
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!existingCategory) { if (!existingCategory) {
throw new NotFoundError('카테고리를 찾을 수 없습니다'); throw new NotFoundError('카테고리를 찾을 수 없습니다');
} }
// layout_image가 요청에 없거나 null이면 기존 값 보존
const updateData = { const updateData = {
...categoryData, ...categoryData,
layout_image: (categoryData.layout_image !== undefined && categoryData.layout_image !== null) layout_image: (categoryData.layout_image !== undefined && categoryData.layout_image !== null)
@@ -135,12 +91,7 @@ exports.updateCategory = asyncHandler(async (req, res) => {
: existingCategory.layout_image : existingCategory.layout_image
}; };
await new Promise((resolve, reject) => { await workplaceModel.updateCategory(categoryId, updateData);
workplaceModel.updateCategory(categoryId, updateData, (err, result) => {
if (err) reject(new DatabaseError('카테고리 수정 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('카테고리 수정 성공', { category_id: categoryId }); logger.info('카테고리 수정 성공', { category_id: categoryId });
@@ -150,20 +101,12 @@ exports.updateCategory = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 카테고리 삭제
*/
exports.deleteCategory = asyncHandler(async (req, res) => { exports.deleteCategory = asyncHandler(async (req, res) => {
const categoryId = req.params.id; const categoryId = req.params.id;
logger.info('카테고리 삭제 요청', { category_id: categoryId }); logger.info('카테고리 삭제 요청', { category_id: categoryId });
await new Promise((resolve, reject) => { await workplaceModel.deleteCategory(categoryId);
workplaceModel.deleteCategory(categoryId, (err, result) => {
if (err) reject(new DatabaseError('카테고리 삭제 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('카테고리 삭제 성공', { category_id: categoryId }); logger.info('카테고리 삭제 성공', { category_id: categoryId });
@@ -175,9 +118,6 @@ exports.deleteCategory = asyncHandler(async (req, res) => {
// ==================== 작업장 관련 ==================== // ==================== 작업장 관련 ====================
/**
* 작업장 생성
*/
exports.createWorkplace = asyncHandler(async (req, res) => { exports.createWorkplace = asyncHandler(async (req, res) => {
const workplaceData = req.body; const workplaceData = req.body;
@@ -187,12 +127,7 @@ exports.createWorkplace = asyncHandler(async (req, res) => {
logger.info('작업장 생성 요청', { name: workplaceData.workplace_name }); logger.info('작업장 생성 요청', { name: workplaceData.workplace_name });
const id = await new Promise((resolve, reject) => { const id = await workplaceModel.createWorkplace(workplaceData);
workplaceModel.createWorkplace(workplaceData, (err, lastID) => {
if (err) reject(new DatabaseError('작업장 생성 중 오류가 발생했습니다'));
else resolve(lastID);
});
});
logger.info('작업장 생성 성공', { workplace_id: id }); logger.info('작업장 생성 성공', { workplace_id: id });
@@ -203,35 +138,12 @@ exports.createWorkplace = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 전체 작업장 조회
*/
exports.getAllWorkplaces = asyncHandler(async (req, res) => { exports.getAllWorkplaces = asyncHandler(async (req, res) => {
const categoryId = req.query.category_id; const categoryId = req.query.category_id;
// 카테고리별 필터링 const rows = categoryId
if (categoryId) { ? await workplaceModel.getWorkplacesByCategory(categoryId)
const rows = await new Promise((resolve, reject) => { : await workplaceModel.getAllWorkplaces();
workplaceModel.getWorkplacesByCategory(categoryId, (err, data) => {
if (err) reject(new DatabaseError('작업장 목록 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
return res.json({
success: true,
data: rows,
message: '작업장 목록 조회 성공'
});
}
// 전체 조회
const rows = await new Promise((resolve, reject) => {
workplaceModel.getAllWorkplaces((err, data) => {
if (err) reject(new DatabaseError('작업장 목록 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -240,16 +152,8 @@ exports.getAllWorkplaces = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 활성 작업장만 조회
*/
exports.getActiveWorkplaces = asyncHandler(async (req, res) => { exports.getActiveWorkplaces = asyncHandler(async (req, res) => {
const rows = await new Promise((resolve, reject) => { const rows = await workplaceModel.getActiveWorkplaces();
workplaceModel.getActiveWorkplaces((err, data) => {
if (err) reject(new DatabaseError('활성 작업장 목록 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -258,18 +162,9 @@ exports.getActiveWorkplaces = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 단일 작업장 조회
*/
exports.getWorkplaceById = asyncHandler(async (req, res) => { exports.getWorkplaceById = asyncHandler(async (req, res) => {
const workplaceId = req.params.id; const workplaceId = req.params.id;
const workplace = await workplaceModel.getWorkplaceById(workplaceId);
const workplace = await new Promise((resolve, reject) => {
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!workplace) { if (!workplace) {
throw new NotFoundError('작업장을 찾을 수 없습니다'); throw new NotFoundError('작업장을 찾을 수 없습니다');
@@ -282,9 +177,6 @@ exports.getWorkplaceById = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 작업장 수정
*/
exports.updateWorkplace = asyncHandler(async (req, res) => { exports.updateWorkplace = asyncHandler(async (req, res) => {
const workplaceId = req.params.id; const workplaceId = req.params.id;
const workplaceData = req.body; const workplaceData = req.body;
@@ -295,19 +187,11 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
logger.info('작업장 수정 요청', { workplace_id: workplaceId }); logger.info('작업장 수정 요청', { workplace_id: workplaceId });
// 기존 작업장 정보 가져오기 const existingWorkplace = await workplaceModel.getWorkplaceById(workplaceId);
const existingWorkplace = await new Promise((resolve, reject) => {
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!existingWorkplace) { if (!existingWorkplace) {
throw new NotFoundError('작업장을 찾을 수 없습니다'); throw new NotFoundError('작업장을 찾을 수 없습니다');
} }
// layout_image가 요청에 없거나 null이면 기존 값 보존
const updateData = { const updateData = {
...workplaceData, ...workplaceData,
layout_image: (workplaceData.layout_image !== undefined && workplaceData.layout_image !== null) layout_image: (workplaceData.layout_image !== undefined && workplaceData.layout_image !== null)
@@ -315,12 +199,7 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
: existingWorkplace.layout_image : existingWorkplace.layout_image
}; };
await new Promise((resolve, reject) => { await workplaceModel.updateWorkplace(workplaceId, updateData);
workplaceModel.updateWorkplace(workplaceId, updateData, (err, result) => {
if (err) reject(new DatabaseError('작업장 수정 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('작업장 수정 성공', { workplace_id: workplaceId }); logger.info('작업장 수정 성공', { workplace_id: workplaceId });
@@ -330,20 +209,12 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 작업장 삭제
*/
exports.deleteWorkplace = asyncHandler(async (req, res) => { exports.deleteWorkplace = asyncHandler(async (req, res) => {
const workplaceId = req.params.id; const workplaceId = req.params.id;
logger.info('작업장 삭제 요청', { workplace_id: workplaceId }); logger.info('작업장 삭제 요청', { workplace_id: workplaceId });
await new Promise((resolve, reject) => { await workplaceModel.deleteWorkplace(workplaceId);
workplaceModel.deleteWorkplace(workplaceId, (err, result) => {
if (err) reject(new DatabaseError('작업장 삭제 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('작업장 삭제 성공', { workplace_id: workplaceId }); logger.info('작업장 삭제 성공', { workplace_id: workplaceId });
@@ -355,9 +226,6 @@ exports.deleteWorkplace = asyncHandler(async (req, res) => {
// ==================== 작업장 지도 영역 관련 ==================== // ==================== 작업장 지도 영역 관련 ====================
/**
* 카테고리 레이아웃 이미지 업로드
*/
exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => { exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
const categoryId = req.params.id; const categoryId = req.params.id;
@@ -369,19 +237,11 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
logger.info('카테고리 레이아웃 이미지 업로드 요청', { category_id: categoryId, path: imagePath }); logger.info('카테고리 레이아웃 이미지 업로드 요청', { category_id: categoryId, path: imagePath });
// 현재 카테고리 정보 가져오기 const category = await workplaceModel.getCategoryById(categoryId);
const category = await new Promise((resolve, reject) => {
workplaceModel.getCategoryById(categoryId, (err, data) => {
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!category) { if (!category) {
throw new NotFoundError('카테고리를 찾을 수 없습니다'); throw new NotFoundError('카테고리를 찾을 수 없습니다');
} }
// 카테고리 정보 업데이트 (이미지 경로만 변경)
const updatedData = { const updatedData = {
category_name: category.category_name, category_name: category.category_name,
description: category.description, description: category.description,
@@ -390,12 +250,7 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
layout_image: imagePath layout_image: imagePath
}; };
await new Promise((resolve, reject) => { await workplaceModel.updateCategory(categoryId, updatedData);
workplaceModel.updateCategory(categoryId, updatedData, (err, result) => {
if (err) reject(new DatabaseError('이미지 경로 저장 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('레이아웃 이미지 업로드 성공', { category_id: categoryId }); logger.info('레이아웃 이미지 업로드 성공', { category_id: categoryId });
@@ -406,9 +261,6 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 작업장 레이아웃 이미지 업로드
*/
exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => { exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
const workplaceId = req.params.id; const workplaceId = req.params.id;
@@ -420,19 +272,11 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
logger.info('작업장 레이아웃 이미지 업로드 요청', { workplace_id: workplaceId, path: imagePath }); logger.info('작업장 레이아웃 이미지 업로드 요청', { workplace_id: workplaceId, path: imagePath });
// 현재 작업장 정보 가져오기 const workplace = await workplaceModel.getWorkplaceById(workplaceId);
const workplace = await new Promise((resolve, reject) => {
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
if (!workplace) { if (!workplace) {
throw new NotFoundError('작업장을 찾을 수 없습니다'); throw new NotFoundError('작업장을 찾을 수 없습니다');
} }
// 작업장 정보 업데이트 (이미지 경로만 변경)
const updatedData = { const updatedData = {
workplace_name: workplace.workplace_name, workplace_name: workplace.workplace_name,
category_id: workplace.category_id, category_id: workplace.category_id,
@@ -443,12 +287,7 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
layout_image: imagePath layout_image: imagePath
}; };
await new Promise((resolve, reject) => { await workplaceModel.updateWorkplace(workplaceId, updatedData);
workplaceModel.updateWorkplace(workplaceId, updatedData, (err, result) => {
if (err) reject(new DatabaseError('이미지 경로 저장 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('작업장 레이아웃 이미지 업로드 성공', { workplace_id: workplaceId }); logger.info('작업장 레이아웃 이미지 업로드 성공', { workplace_id: workplaceId });
@@ -459,9 +298,6 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 지도 영역 생성
*/
exports.createMapRegion = asyncHandler(async (req, res) => { exports.createMapRegion = asyncHandler(async (req, res) => {
const regionData = req.body; const regionData = req.body;
@@ -471,12 +307,7 @@ exports.createMapRegion = asyncHandler(async (req, res) => {
logger.info('지도 영역 생성 요청', { workplace_id: regionData.workplace_id }); logger.info('지도 영역 생성 요청', { workplace_id: regionData.workplace_id });
const id = await new Promise((resolve, reject) => { const id = await workplaceModel.createMapRegion(regionData);
workplaceModel.createMapRegion(regionData, (err, lastID) => {
if (err) reject(new DatabaseError('지도 영역 생성 중 오류가 발생했습니다'));
else resolve(lastID);
});
});
logger.info('지도 영역 생성 성공', { region_id: id }); logger.info('지도 영역 생성 성공', { region_id: id });
@@ -487,18 +318,9 @@ exports.createMapRegion = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 카테고리별 지도 영역 조회 (작업장 정보 포함)
*/
exports.getMapRegionsByCategory = asyncHandler(async (req, res) => { exports.getMapRegionsByCategory = asyncHandler(async (req, res) => {
const categoryId = req.params.categoryId; const categoryId = req.params.categoryId;
const rows = await workplaceModel.getMapRegionsByCategory(categoryId);
const rows = await new Promise((resolve, reject) => {
workplaceModel.getMapRegionsByCategory(categoryId, (err, data) => {
if (err) reject(new DatabaseError('지도 영역 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -507,18 +329,9 @@ exports.getMapRegionsByCategory = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 작업장별 지도 영역 조회
*/
exports.getMapRegionByWorkplace = asyncHandler(async (req, res) => { exports.getMapRegionByWorkplace = asyncHandler(async (req, res) => {
const workplaceId = req.params.workplaceId; const workplaceId = req.params.workplaceId;
const region = await workplaceModel.getMapRegionByWorkplace(workplaceId);
const region = await new Promise((resolve, reject) => {
workplaceModel.getMapRegionByWorkplace(workplaceId, (err, data) => {
if (err) reject(new DatabaseError('지도 영역 조회 중 오류가 발생했습니다'));
else resolve(data);
});
});
res.json({ res.json({
success: true, success: true,
@@ -527,21 +340,13 @@ exports.getMapRegionByWorkplace = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 지도 영역 수정
*/
exports.updateMapRegion = asyncHandler(async (req, res) => { exports.updateMapRegion = asyncHandler(async (req, res) => {
const regionId = req.params.id; const regionId = req.params.id;
const regionData = req.body; const regionData = req.body;
logger.info('지도 영역 수정 요청', { region_id: regionId }); logger.info('지도 영역 수정 요청', { region_id: regionId });
await new Promise((resolve, reject) => { await workplaceModel.updateMapRegion(regionId, regionData);
workplaceModel.updateMapRegion(regionId, regionData, (err, result) => {
if (err) reject(new DatabaseError('지도 영역 수정 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('지도 영역 수정 성공', { region_id: regionId }); logger.info('지도 영역 수정 성공', { region_id: regionId });
@@ -551,20 +356,12 @@ exports.updateMapRegion = asyncHandler(async (req, res) => {
}); });
}); });
/**
* 지도 영역 삭제
*/
exports.deleteMapRegion = asyncHandler(async (req, res) => { exports.deleteMapRegion = asyncHandler(async (req, res) => {
const regionId = req.params.id; const regionId = req.params.id;
logger.info('지도 영역 삭제 요청', { region_id: regionId }); logger.info('지도 영역 삭제 요청', { region_id: regionId });
await new Promise((resolve, reject) => { await workplaceModel.deleteMapRegion(regionId);
workplaceModel.deleteMapRegion(regionId, (err, result) => {
if (err) reject(new DatabaseError('지도 영역 삭제 중 오류가 발생했습니다'));
else resolve(result);
});
});
logger.info('지도 영역 삭제 성공', { region_id: regionId }); logger.info('지도 영역 삭제 성공', { region_id: regionId });

View File

@@ -2,8 +2,6 @@ const { getDb } = require('../dbPool');
/** /**
* 1. 여러 개의 이슈 보고서를 트랜잭션으로 생성합니다. * 1. 여러 개의 이슈 보고서를 트랜잭션으로 생성합니다.
* @param {Array<object>} reports - 생성할 보고서 데이터 배열
* @returns {Promise<Array<number>>} - 삽입된 ID 배열
*/ */
const createMany = async (reports) => { const createMany = async (reports) => {
const db = await getDb(); const db = await getDb();
@@ -13,7 +11,7 @@ const createMany = async (reports) => {
const insertedIds = []; const insertedIds = [];
const sql = ` const sql = `
INSERT INTO DailyIssueReports INSERT INTO DailyIssueReports
(date, worker_id, project_id, start_time, end_time, issue_type_id) (date, worker_id, project_id, start_time, end_time, issue_type_id)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
`; `;
@@ -36,119 +34,71 @@ const createMany = async (reports) => {
}; };
/** /**
* 2. 특정 날짜의 전체 이슈 목록 조회 (Promise 기반) * 2. 특정 날짜의 전체 이슈 목록 조회
*/ */
const getAllByDate = async (date) => { const getAllByDate = async (date) => {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(
const [rows] = await db.query( `SELECT
`SELECT d.id, d.date, w.worker_name, p.project_name, d.start_time, d.end_time,
d.id, d.date, w.worker_name, p.project_name, d.start_time, d.end_time, t.category, t.subcategory, d.description
t.category, t.subcategory, d.description FROM DailyIssueReports d
FROM DailyIssueReports d LEFT JOIN workers w ON d.worker_id = w.worker_id
LEFT JOIN workers w ON d.worker_id = w.worker_id LEFT JOIN projects p ON d.project_id = p.project_id
LEFT JOIN projects p ON d.project_id = p.project_id LEFT JOIN IssueTypes t ON d.issue_type_id = t.issue_type_id
LEFT JOIN IssueTypes t ON d.issue_type_id = t.issue_type_id WHERE d.date = ?
WHERE d.date = ? ORDER BY d.start_time ASC`,
ORDER BY d.start_time ASC`, [date]
[date] );
); return rows;
return rows;
} catch (err) {
console.error(`[Model] ${date} 이슈 목록 조회 오류:`, err);
throw new Error('데이터베이스에서 이슈 목록을 조회하는 중 오류가 발생했습니다.');
}
}; };
/** /**
* 3. 단일 조회 (선택사항: 컨트롤러에서 사용 중) * 3. 단일 조회
*/ */
const getById = async (id, callback) => { const getById = async (id) => {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(`SELECT id, date, worker_id, project_id, issue_type_id, description, created_at, start_time, end_time FROM DailyIssueReports WHERE id = ?`, [id]);
const [rows] = await db.query(`SELECT id, date, worker_id, project_id, issue_type_id, description, created_at, start_time, end_time FROM DailyIssueReports WHERE id = ?`, [id]); return rows[0];
callback(null, rows[0]);
} catch (err) {
callback(err);
}
}; };
/** /**
* 4. 수정 * 4. 수정
*/ */
const update = async (id, data, callback) => { const update = async (id, data) => {
try { const db = await getDb();
const db = await getDb();
const fields = []; const fields = [];
const values = []; const values = [];
for (const key in data) { for (const key in data) {
fields.push(`${key} = ?`); fields.push(`${key} = ?`);
values.push(data[key]); values.push(data[key]);
}
values.push(id); // 마지막에 id
const [result] = await db.query(
`UPDATE DailyIssueReports SET ${fields.join(', ')} WHERE id = ?`,
values
);
callback(null, result.affectedRows);
} catch (err) {
callback(err);
} }
values.push(id);
const [result] = await db.query(
`UPDATE DailyIssueReports SET ${fields.join(', ')} WHERE id = ?`,
values
);
return result.affectedRows;
}; };
/** /**
* 5. 삭제 (Promise 기반) * 5. 삭제
*/ */
const remove = async (id) => { const remove = async (id) => {
try { const db = await getDb();
const db = await getDb(); const [result] = await db.query(`DELETE FROM DailyIssueReports WHERE id = ?`, [id]);
const [result] = await db.query(`DELETE FROM DailyIssueReports WHERE id = ?`, [id]); return result.affectedRows;
return result.affectedRows;
} catch (err) {
console.error(`[Model] 이슈(id: ${id}) 삭제 오류:`, err);
throw new Error('데이터베이스에서 이슈를 삭제하는 중 오류가 발생했습니다.');
}
}; };
// V1 함수들은 점진적으로 제거 예정
const create = async (report, callback) => {
try {
const db = await getDb();
const {
date,
worker_id,
project_id,
start_time,
end_time,
issue_type_id,
description = null // 선택값 처리
} = report;
const [result] = await db.query(
`INSERT INTO DailyIssueReports
(date, worker_id, project_id, start_time, end_time, issue_type_id, description)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[date, worker_id, project_id, start_time, end_time, issue_type_id, description]
);
callback(null, result.insertId);
} catch (err) {
callback(err);
}
};
module.exports = { module.exports = {
createMany, // 신규 createMany,
getAllByDate, getAllByDate,
remove,
// 레거시 호환성을 위해 V1 함수들 임시 유지
create: (report, callback) => createMany([report]).then(ids => callback(null, ids[0])).catch(err => callback(err)),
getById, getById,
update, update,
}; remove
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,160 +0,0 @@
// models/pageAccessModel.js
const db = require('../db/connection');
const PageAccessModel = {
// 사용자의 페이지 권한 조회
getUserPageAccess: (userId, callback) => {
const sql = `
SELECT
p.id,
p.page_key,
p.page_name,
p.page_path,
p.category,
p.is_admin_only,
COALESCE(upa.can_access, 0) as can_access,
upa.granted_at,
upa.granted_by,
granter.username as granted_by_username
FROM pages p
LEFT JOIN user_page_access upa ON p.id = upa.page_id AND upa.user_id = ?
LEFT JOIN users granter ON upa.granted_by = granter.user_id
WHERE p.is_admin_only = 0
ORDER BY p.category, p.display_order
`;
db.query(sql, [userId], callback);
},
// 모든 페이지 목록 조회
getAllPages: (callback) => {
const sql = `
SELECT
id,
page_key,
page_name,
page_path,
category,
description,
is_admin_only,
display_order
FROM pages
WHERE is_admin_only = 0
ORDER BY category, display_order
`;
db.query(sql, callback);
},
// 페이지 권한 부여
grantPageAccess: (userId, pageId, grantedBy, callback) => {
const sql = `
INSERT INTO user_page_access (user_id, page_id, can_access, granted_by, granted_at)
VALUES (?, ?, 1, ?, NOW())
ON DUPLICATE KEY UPDATE
can_access = 1,
granted_by = ?,
granted_at = NOW()
`;
db.query(sql, [userId, pageId, grantedBy, grantedBy], callback);
},
// 페이지 권한 회수
revokePageAccess: (userId, pageId, callback) => {
const sql = `
DELETE FROM user_page_access
WHERE user_id = ? AND page_id = ?
`;
db.query(sql, [userId, pageId], callback);
},
// 여러 페이지 권한 일괄 설정
setUserPageAccess: (userId, pageIds, grantedBy, callback) => {
db.beginTransaction((err) => {
if (err) return callback(err);
// 기존 권한 모두 삭제
const deleteSql = 'DELETE FROM user_page_access WHERE user_id = ?';
db.query(deleteSql, [userId], (err) => {
if (err) {
return db.rollback(() => callback(err));
}
// 새 권한이 없으면 커밋하고 종료
if (!pageIds || pageIds.length === 0) {
return db.commit((err) => {
if (err) return db.rollback(() => callback(err));
callback(null, { affectedRows: 0 });
});
}
// 새 권한 추가
const values = pageIds.map(pageId => [userId, pageId, 1, grantedBy]);
const insertSql = `
INSERT INTO user_page_access (user_id, page_id, can_access, granted_by, granted_at)
VALUES ?
`;
db.query(insertSql, [values], (err, result) => {
if (err) {
return db.rollback(() => callback(err));
}
db.commit((err) => {
if (err) return db.rollback(() => callback(err));
callback(null, result);
});
});
});
});
},
// 특정 페이지 접근 권한 확인
checkPageAccess: (userId, pageKey, callback) => {
const sql = `
SELECT
COALESCE(upa.can_access, 0) as can_access,
p.is_admin_only
FROM pages p
LEFT JOIN user_page_access upa ON p.id = upa.page_id AND upa.user_id = ?
WHERE p.page_key = ?
`;
db.query(sql, [userId, pageKey], (err, results) => {
if (err) return callback(err);
if (results.length === 0) return callback(null, { can_access: false });
callback(null, results[0]);
});
},
// 계정이 있는 작업자 목록 조회 (권한 관리용)
getUsersWithAccounts: (callback) => {
const sql = `
SELECT
u.user_id,
u.username,
u.name,
u.role_id,
r.name as role_name,
u.worker_id,
w.worker_name,
w.job_type,
COUNT(upa.page_id) as granted_pages_count
FROM users u
LEFT JOIN roles r ON u.role_id = r.id
LEFT JOIN workers w ON u.worker_id = w.worker_id
LEFT JOIN user_page_access upa ON u.user_id = upa.user_id AND upa.can_access = 1
WHERE u.is_active = 1
AND u.role_id IN (4, 5)
GROUP BY u.user_id
ORDER BY w.worker_name, u.username
`;
db.query(sql, callback);
}
};
module.exports = PageAccessModel;

File diff suppressed because it is too large Load Diff

View File

@@ -6,11 +6,10 @@ const TbmTransferModel = {
* 작업자 이동 실행 (보내기/빼오기) * 작업자 이동 실행 (보내기/빼오기)
* 트랜잭션: source work_hours 업데이트 + dest INSERT + 로그 INSERT * 트랜잭션: source work_hours 업데이트 + dest INSERT + 로그 INSERT
*/ */
createTransfer: async (transferData, callback) => { async createTransfer(transferData) {
let conn; const db = await getDb();
const conn = await db.getConnection();
try { try {
const db = await getDb();
conn = await db.getConnection();
await conn.beginTransaction(); await conn.beginTransaction();
const { const {
@@ -27,8 +26,7 @@ const TbmTransferModel = {
if (sourceRows.length === 0) { if (sourceRows.length === 0) {
await conn.rollback(); await conn.rollback();
conn.release(); return { success: false, message: '원본 세션에서 해당 작업자를 찾을 수 없습니다.' };
return callback(null, { success: false, message: '원본 세션에서 해당 작업자를 찾을 수 없습니다.' });
} }
const currentHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours); const currentHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours);
@@ -36,8 +34,7 @@ const TbmTransferModel = {
if (newSourceHours < 0) { if (newSourceHours < 0) {
await conn.rollback(); await conn.rollback();
conn.release(); return { success: false, message: '이동 시간이 현재 배정 시간보다 큽니다.' };
return callback(null, { success: false, message: '이동 시간이 현재 배정 시간보다 큽니다.' });
} }
await conn.query( await conn.query(
@@ -52,14 +49,12 @@ const TbmTransferModel = {
); );
if (destRows.length > 0) { if (destRows.length > 0) {
// 이미 있으면 시간만 추가
const existingHours = destRows[0].work_hours === null ? 8 : parseFloat(destRows[0].work_hours); const existingHours = destRows[0].work_hours === null ? 8 : parseFloat(destRows[0].work_hours);
await conn.query( await conn.query(
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?', 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?',
[existingHours + parseFloat(hours), dest_session_id, worker_id] [existingHours + parseFloat(hours), dest_session_id, worker_id]
); );
} else { } else {
// 새로 INSERT
await conn.query( await conn.query(
`INSERT INTO tbm_team_assignments `INSERT INTO tbm_team_assignments
(session_id, worker_id, work_hours, project_id, work_type_id, task_id, workplace_category_id, workplace_id, is_present) (session_id, worker_id, work_hours, project_id, work_type_id, task_id, workplace_category_id, workplace_id, is_present)
@@ -89,7 +84,6 @@ const TbmTransferModel = {
const totalHours = totalRows[0] ? parseFloat(totalRows[0].total_hours) : 0; const totalHours = totalRows[0] ? parseFloat(totalRows[0].total_hours) : 0;
await conn.commit(); await conn.commit();
conn.release();
const result = { const result = {
success: true, success: true,
@@ -101,24 +95,22 @@ const TbmTransferModel = {
result.warning = `해당 작업자의 당일 합계가 ${totalHours}h입니다 (8h 초과).`; result.warning = `해당 작업자의 당일 합계가 ${totalHours}h입니다 (8h 초과).`;
} }
callback(null, result); return result;
} catch (err) { } catch (err) {
if (conn) { try { await conn.rollback(); } catch (e) {}
try { await conn.rollback(); } catch (e) {} throw err;
conn.release(); } finally {
} conn.release();
callback(err);
} }
}, },
/** /**
* 이동 취소 (원복) * 이동 취소 (원복)
*/ */
cancelTransfer: async (transferId, callback) => { async cancelTransfer(transferId) {
let conn; const db = await getDb();
const conn = await db.getConnection();
try { try {
const db = await getDb();
conn = await db.getConnection();
await conn.beginTransaction(); await conn.beginTransaction();
// 1. 이동 로그 조회 // 1. 이동 로그 조회
@@ -129,8 +121,7 @@ const TbmTransferModel = {
if (transfers.length === 0) { if (transfers.length === 0) {
await conn.rollback(); await conn.rollback();
conn.release(); return { success: false, message: '이동 기록을 찾을 수 없습니다.' };
return callback(null, { success: false, message: '이동 기록을 찾을 수 없습니다.' });
} }
const t = transfers[0]; const t = transfers[0];
@@ -146,7 +137,6 @@ const TbmTransferModel = {
const newDestHours = destHours - parseFloat(t.hours); const newDestHours = destHours - parseFloat(t.hours);
if (newDestHours <= 0) { if (newDestHours <= 0) {
// 삭제
await conn.query( await conn.query(
'DELETE FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', 'DELETE FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?',
[t.dest_session_id, t.worker_id] [t.dest_session_id, t.worker_id]
@@ -168,7 +158,6 @@ const TbmTransferModel = {
if (sourceRows.length > 0) { if (sourceRows.length > 0) {
const sourceHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours); const sourceHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours);
const restoredHours = sourceHours + parseFloat(t.hours); const restoredHours = sourceHours + parseFloat(t.hours);
// 8이면 NULL로 복원 (종일)
await conn.query( await conn.query(
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?', 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?',
[restoredHours >= 8 ? null : restoredHours, t.source_session_id, t.worker_id] [restoredHours >= 8 ? null : restoredHours, t.source_session_id, t.worker_id]
@@ -179,115 +168,103 @@ const TbmTransferModel = {
await conn.query('DELETE FROM tbm_transfers WHERE transfer_id = ?', [transferId]); await conn.query('DELETE FROM tbm_transfers WHERE transfer_id = ?', [transferId]);
await conn.commit(); await conn.commit();
conn.release(); return { success: true };
callback(null, { success: true });
} catch (err) { } catch (err) {
if (conn) { try { await conn.rollback(); } catch (e) {}
try { await conn.rollback(); } catch (e) {} throw err;
conn.release(); } finally {
} conn.release();
callback(err);
} }
}, },
/** /**
* 당일 이동 내역 조회 * 당일 이동 내역 조회
*/ */
getTransfersByDate: async (date, callback) => { async getTransfersByDate(date) {
try { const db = await getDb();
const db = await getDb(); const sql = `
const sql = ` SELECT
SELECT t.*,
t.*, w.worker_name,
w.worker_name, w.job_type,
w.job_type, sl.worker_name as source_leader_name,
sl.worker_name as source_leader_name, dl.worker_name as dest_leader_name,
dl.worker_name as dest_leader_name, u.name as initiated_by_name
u.name as initiated_by_name FROM tbm_transfers t
FROM tbm_transfers t INNER JOIN workers w ON t.worker_id = w.worker_id
INNER JOIN workers w ON t.worker_id = w.worker_id LEFT JOIN tbm_sessions ss ON t.source_session_id = ss.session_id
LEFT JOIN tbm_sessions ss ON t.source_session_id = ss.session_id LEFT JOIN workers sl ON ss.leader_id = sl.worker_id
LEFT JOIN workers sl ON ss.leader_id = sl.worker_id LEFT JOIN tbm_sessions ds ON t.dest_session_id = ds.session_id
LEFT JOIN tbm_sessions ds ON t.dest_session_id = ds.session_id LEFT JOIN workers dl ON ds.leader_id = dl.worker_id
LEFT JOIN workers dl ON ds.leader_id = dl.worker_id LEFT JOIN sso_users u ON t.initiated_by = u.user_id
LEFT JOIN sso_users u ON t.initiated_by = u.user_id WHERE t.transfer_date = ?
WHERE t.transfer_date = ? ORDER BY t.created_at DESC
ORDER BY t.created_at DESC `;
`; const [rows] = await db.query(sql, [date]);
const [rows] = await db.query(sql, [date]); return rows;
callback(null, rows);
} catch (err) {
callback(err);
}
}, },
/** /**
* 당일 전 작업자 배정 현황 조회 * 당일 전 작업자 배정 현황 조회
*/ */
getWorkerAssignmentsByDate: async (date, callback) => { async getWorkerAssignmentsByDate(date) {
try { const db = await getDb();
const db = await getDb();
// 1. 해당 날짜의 모든 배정 가져오기 // 1. 해당 날짜의 모든 배정 가져오기
const [assignments] = await db.query(` const [assignments] = await db.query(`
SELECT SELECT
ta.worker_id, ta.worker_id,
ta.session_id, ta.session_id,
ta.work_hours, ta.work_hours,
w.worker_name, w.worker_name,
w.job_type, w.job_type,
s.leader_id, s.leader_id,
lw.worker_name as leader_name, lw.worker_name as leader_name,
s.status as session_status s.status as session_status
FROM tbm_team_assignments ta FROM tbm_team_assignments ta
INNER JOIN tbm_sessions s ON ta.session_id = s.session_id INNER JOIN tbm_sessions s ON ta.session_id = s.session_id
INNER JOIN workers w ON ta.worker_id = w.worker_id INNER JOIN workers w ON ta.worker_id = w.worker_id
LEFT JOIN workers lw ON s.leader_id = lw.worker_id LEFT JOIN workers lw ON s.leader_id = lw.worker_id
WHERE s.session_date = ? WHERE s.session_date = ?
ORDER BY w.worker_name ORDER BY w.worker_name
`, [date]); `, [date]);
// 2. 모든 작업자 가져오기 (배정 안 된 사람도 포함) // 2. 모든 작업자 가져오기 (배정 안 된 사람도 포함)
const [allWorkers] = await db.query( const [allWorkers] = await db.query(
"SELECT worker_id, worker_name, job_type FROM workers WHERE status = 'active' AND department = '생산' ORDER BY worker_name" "SELECT worker_id, worker_name, job_type FROM workers WHERE status = 'active' AND department = '생산' ORDER BY worker_name"
); );
// 3. 작업자별 배정 현황 구성 // 3. 작업자별 배정 현황 구성
const workerMap = {}; const workerMap = {};
allWorkers.forEach(w => { allWorkers.forEach(w => {
workerMap[w.worker_id] = { workerMap[w.worker_id] = {
worker_id: w.worker_id, worker_id: w.worker_id,
worker_name: w.worker_name, worker_name: w.worker_name,
job_type: w.job_type, job_type: w.job_type,
sessions: [], sessions: [],
total_hours: 0, total_hours: 0,
available: true available: true
}; };
}); });
assignments.forEach(a => { assignments.forEach(a => {
const hours = a.work_hours === null ? 8 : parseFloat(a.work_hours); const hours = a.work_hours === null ? 8 : parseFloat(a.work_hours);
if (workerMap[a.worker_id]) { if (workerMap[a.worker_id]) {
workerMap[a.worker_id].sessions.push({ workerMap[a.worker_id].sessions.push({
session_id: a.session_id, session_id: a.session_id,
leader_name: a.leader_name, leader_name: a.leader_name,
work_hours: hours, work_hours: hours,
session_status: a.session_status session_status: a.session_status
}); });
workerMap[a.worker_id].total_hours += hours; workerMap[a.worker_id].total_hours += hours;
} }
}); });
// available 판단 Object.values(workerMap).forEach(w => {
Object.values(workerMap).forEach(w => { w.available = w.total_hours < 8;
w.available = w.total_hours < 8; });
});
callback(null, Object.values(workerMap)); return Object.values(workerMap);
} catch (err) {
callback(err);
}
} }
}; };

View File

@@ -9,251 +9,190 @@ const vacationBalanceModel = {
/** /**
* 특정 작업자의 모든 휴가 잔액 조회 (특정 연도) * 특정 작업자의 모든 휴가 잔액 조회 (특정 연도)
*/ */
async getByWorkerAndYear(workerId, year, callback) { async getByWorkerAndYear(workerId, year) {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(`
const query = ` SELECT
SELECT vbd.*,
vbd.*, vt.type_name,
vt.type_name, vt.type_code,
vt.type_code, vt.priority,
vt.priority, vt.is_special
vt.is_special FROM vacation_balance_details vbd
FROM vacation_balance_details vbd INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id WHERE vbd.worker_id = ? AND vbd.year = ?
WHERE vbd.worker_id = ? AND vbd.year = ? ORDER BY vt.priority ASC, vt.type_name ASC
ORDER BY vt.priority ASC, vt.type_name ASC `, [workerId, year]);
`; return rows;
const [rows] = await db.query(query, [workerId, year]);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 특정 작업자의 특정 휴가 유형 잔액 조회 * 특정 작업자의 특정 휴가 유형 잔액 조회
*/ */
async getByWorkerTypeYear(workerId, vacationTypeId, year, callback) { async getByWorkerTypeYear(workerId, vacationTypeId, year) {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(`
const query = ` SELECT
SELECT vbd.*,
vbd.*, vt.type_name,
vt.type_name, vt.type_code
vt.type_code FROM vacation_balance_details vbd
FROM vacation_balance_details vbd INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id WHERE vbd.worker_id = ?
WHERE vbd.worker_id = ? AND vbd.vacation_type_id = ?
AND vbd.vacation_type_id = ? AND vbd.year = ?
AND vbd.year = ? `, [workerId, vacationTypeId, year]);
`; return rows;
const [rows] = await db.query(query, [workerId, vacationTypeId, year]);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 모든 작업자의 휴가 잔액 조회 (특정 연도) * 모든 작업자의 휴가 잔액 조회 (특정 연도)
* - 연간 연차 현황 차트용
*/ */
async getAllByYear(year, callback) { async getAllByYear(year) {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(`
const query = ` SELECT
SELECT vbd.*,
vbd.*, w.worker_name,
w.worker_name, w.employment_status,
w.employment_status, vt.type_name,
vt.type_name, vt.type_code,
vt.type_code, vt.priority
vt.priority FROM vacation_balance_details vbd
FROM vacation_balance_details vbd INNER JOIN workers w ON vbd.worker_id = w.worker_id
INNER JOIN workers w ON vbd.worker_id = w.worker_id INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id WHERE vbd.year = ?
WHERE vbd.year = ? AND w.employment_status = 'employed'
AND w.employment_status = 'employed' ORDER BY w.worker_name ASC, vt.priority ASC
ORDER BY w.worker_name ASC, vt.priority ASC `, [year]);
`; return rows;
const [rows] = await db.query(query, [year]);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 잔액 생성 * 휴가 잔액 생성
*/ */
async create(balanceData, callback) { async create(balanceData) {
try { const db = await getDb();
const db = await getDb(); const [result] = await db.query(`INSERT INTO vacation_balance_details SET ?`, balanceData);
const query = `INSERT INTO vacation_balance_details SET ?`; return result;
const [rows] = await db.query(query, balanceData);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 잔액 수정 * 휴가 잔액 수정
*/ */
async update(id, updateData, callback) { async update(id, updateData) {
try { const db = await getDb();
const db = await getDb(); const [result] = await db.query(`UPDATE vacation_balance_details SET ? WHERE id = ?`, [updateData, id]);
const query = `UPDATE vacation_balance_details SET ? WHERE id = ?`; return result;
const [rows] = await db.query(query, [updateData, id]);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 잔액 삭제 * 휴가 잔액 삭제
*/ */
async delete(id, callback) { async delete(id) {
try { const db = await getDb();
const db = await getDb(); const [result] = await db.query(`DELETE FROM vacation_balance_details WHERE id = ?`, [id]);
const query = `DELETE FROM vacation_balance_details WHERE id = ?`; return result;
const [rows] = await db.query(query, [id]);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 작업자의 휴가 사용 일수 업데이트 (차감) * 작업자의 휴가 사용 일수 업데이트 (차감)
* - 휴가 신청 승인 시 호출
*/ */
async deductDays(workerId, vacationTypeId, year, daysToDeduct, callback) { async deductDays(workerId, vacationTypeId, year, daysToDeduct) {
try { const db = await getDb();
const db = await getDb(); const [result] = await db.query(`
const query = ` UPDATE vacation_balance_details
UPDATE vacation_balance_details SET used_days = used_days + ?,
SET used_days = used_days + ?, updated_at = NOW()
updated_at = NOW() WHERE worker_id = ?
WHERE worker_id = ? AND vacation_type_id = ?
AND vacation_type_id = ? AND year = ?
AND year = ? `, [daysToDeduct, workerId, vacationTypeId, year]);
`; return result;
const [rows] = await db.query(query, [daysToDeduct, workerId, vacationTypeId, year]);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 작업자의 휴가 사용 일수 복구 (취소) * 작업자의 휴가 사용 일수 복구 (취소)
* - 휴가 신청 취소/거부 시 호출
*/ */
async restoreDays(workerId, vacationTypeId, year, daysToRestore, callback) { async restoreDays(workerId, vacationTypeId, year, daysToRestore) {
try { const db = await getDb();
const db = await getDb(); const [result] = await db.query(`
const query = ` UPDATE vacation_balance_details
UPDATE vacation_balance_details SET used_days = GREATEST(0, used_days - ?),
SET used_days = GREATEST(0, used_days - ?), updated_at = NOW()
updated_at = NOW() WHERE worker_id = ?
WHERE worker_id = ? AND vacation_type_id = ?
AND vacation_type_id = ? AND year = ?
AND year = ? `, [daysToRestore, workerId, vacationTypeId, year]);
`; return result;
const [rows] = await db.query(query, [daysToRestore, workerId, vacationTypeId, year]);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 특정 작업자의 사용 가능한 휴가 일수 확인 * 특정 작업자의 사용 가능한 휴가 일수 확인
* - 우선순위가 높은 순서대로 차감 가능 여부 확인
*/ */
async getAvailableVacationDays(workerId, year, callback) { async getAvailableVacationDays(workerId, year) {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(`
const query = ` SELECT
SELECT vbd.id,
vbd.id, vbd.vacation_type_id,
vbd.vacation_type_id, vt.type_name,
vt.type_name, vt.type_code,
vt.type_code, vt.priority,
vt.priority, vbd.total_days,
vbd.total_days, vbd.used_days,
vbd.used_days, vbd.remaining_days
vbd.remaining_days FROM vacation_balance_details vbd
FROM vacation_balance_details vbd INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id WHERE vbd.worker_id = ?
WHERE vbd.worker_id = ? AND vbd.year = ?
AND vbd.year = ? AND vbd.remaining_days > 0
AND vbd.remaining_days > 0 ORDER BY vt.priority ASC
ORDER BY vt.priority ASC `, [workerId, year]);
`; return rows;
const [rows] = await db.query(query, [workerId, year]);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 작업자별 휴가 잔액 일괄 생성 (연도별) * 작업자별 휴가 잔액 일괄 생성 (연도별)
* - 매년 초 또는 입사 시 사용
*/ */
async bulkCreate(balances, callback) { async bulkCreate(balances) {
try { if (!balances || balances.length === 0) {
const db = await getDb(); throw new Error('생성할 휴가 잔액 데이터가 없습니다');
if (!balances || balances.length === 0) {
return callback(new Error('생성할 휴가 잔액 데이터가 없습니다'));
}
const query = `INSERT INTO vacation_balance_details
(worker_id, vacation_type_id, year, total_days, used_days, notes, created_by)
VALUES ?`;
const values = balances.map(b => [
b.worker_id,
b.vacation_type_id,
b.year,
b.total_days || 0,
b.used_days || 0,
b.notes || null,
b.created_by
]);
const [rows] = await db.query(query, [values]);
callback(null, rows);
} catch (error) {
callback(error);
} }
const db = await getDb();
const query = `INSERT INTO vacation_balance_details
(worker_id, vacation_type_id, year, total_days, used_days, notes, created_by)
VALUES ?`;
const values = balances.map(b => [
b.worker_id,
b.vacation_type_id,
b.year,
b.total_days || 0,
b.used_days || 0,
b.notes || null,
b.created_by
]);
const [result] = await db.query(query, [values]);
return result;
}, },
/** /**
* 근속년수 기반 연차 일수 계산 (한국 근로기준법) * 근속년수 기반 연차 일수 계산 (한국 근로기준법)
* @param {Date} hireDate - 입사일
* @param {number} targetYear - 대상 연도
* @returns {number} - 부여받을 연차 일수
*/ */
calculateAnnualLeaveDays(hireDate, targetYear) { calculateAnnualLeaveDays(hireDate, targetYear) {
const hire = new Date(hireDate); const hire = new Date(hireDate);
const targetDate = new Date(targetYear, 0, 1); const targetDate = new Date(targetYear, 0, 1);
// 근속 월수 계산
const monthsDiff = (targetDate.getFullYear() - hire.getFullYear()) * 12 const monthsDiff = (targetDate.getFullYear() - hire.getFullYear()) * 12
+ (targetDate.getMonth() - hire.getMonth()); + (targetDate.getMonth() - hire.getMonth());
// 1년 미만: 월 1일
if (monthsDiff < 12) { if (monthsDiff < 12) {
return Math.floor(monthsDiff); return Math.floor(monthsDiff);
} }
// 1년 이상: 15일 기본 + 2년마다 1일 추가 (최대 25일)
const yearsWorked = Math.floor(monthsDiff / 12); const yearsWorked = Math.floor(monthsDiff / 12);
const additionalDays = Math.floor((yearsWorked - 1) / 2); const additionalDays = Math.floor((yearsWorked - 1) / 2);
@@ -261,17 +200,11 @@ const vacationBalanceModel = {
}, },
/** /**
* 휴가 사용 시 우선순위에 따라 잔액에서 차감 (Promise 버전) * 휴가 사용 시 우선순위에 따라 잔액에서 차감
* - 일일 근태 기록 저장 시 호출
* @param {number} workerId - 작업자 ID
* @param {number} year - 연도
* @param {number} daysToDeduct - 차감할 일수 (1, 0.5, 0.25)
* @returns {Promise<Object>} - 차감 결과
*/ */
async deductByPriority(workerId, year, daysToDeduct) { async deductByPriority(workerId, year, daysToDeduct) {
const db = await getDb(); const db = await getDb();
// 우선순위순으로 잔여 일수가 있는 잔액 조회
const [balances] = await db.query(` const [balances] = await db.query(`
SELECT vbd.id, vbd.vacation_type_id, vbd.total_days, vbd.used_days, SELECT vbd.id, vbd.vacation_type_id, vbd.total_days, vbd.used_days,
(vbd.total_days - vbd.used_days) as remaining_days, (vbd.total_days - vbd.used_days) as remaining_days,
@@ -284,7 +217,6 @@ const vacationBalanceModel = {
`, [workerId, year]); `, [workerId, year]);
if (balances.length === 0) { if (balances.length === 0) {
// 잔액이 없어도 일단 기록은 저장 (경고만)
console.warn(`[VacationBalance] 작업자 ${workerId}${year}년 휴가 잔액이 없습니다`); console.warn(`[VacationBalance] 작업자 ${workerId}${year}년 휴가 잔액이 없습니다`);
return { success: false, message: '휴가 잔액이 없습니다', deducted: 0 }; return { success: false, message: '휴가 잔액이 없습니다', deducted: 0 };
} }
@@ -321,16 +253,11 @@ const vacationBalanceModel = {
}, },
/** /**
* 휴가 취소 시 우선순위 역순으로 복구 (Promise 버전) * 휴가 취소 시 우선순위 역순으로 복구
* @param {number} workerId - 작업자 ID
* @param {number} year - 연도
* @param {number} daysToRestore - 복구할 일수
* @returns {Promise<Object>} - 복구 결과
*/ */
async restoreByPriority(workerId, year, daysToRestore) { async restoreByPriority(workerId, year, daysToRestore) {
const db = await getDb(); const db = await getDb();
// 우선순위 역순으로 사용 일수가 있는 잔액 조회 (나중에 차감된 것부터 복구)
const [balances] = await db.query(` const [balances] = await db.query(`
SELECT vbd.id, vbd.vacation_type_id, vbd.used_days, SELECT vbd.id, vbd.vacation_type_id, vbd.used_days,
vt.type_code, vt.type_name, vt.priority vt.type_code, vt.type_name, vt.priority
@@ -375,25 +302,20 @@ const vacationBalanceModel = {
/** /**
* 특정 ID로 휴가 잔액 조회 * 특정 ID로 휴가 잔액 조회
*/ */
async getById(id, callback) { async getById(id) {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(`
const query = ` SELECT
SELECT vbd.*,
vbd.*, w.worker_name,
w.worker_name, vt.type_name,
vt.type_name, vt.type_code
vt.type_code FROM vacation_balance_details vbd
FROM vacation_balance_details vbd INNER JOIN workers w ON vbd.worker_id = w.worker_id
INNER JOIN workers w ON vbd.worker_id = w.worker_id INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id WHERE vbd.id = ?
WHERE vbd.id = ? `, [id]);
`; return rows;
const [rows] = await db.query(query, [id]);
callback(null, rows);
} catch (error) {
callback(error);
}
} }
}; };

View File

@@ -9,123 +9,97 @@ const vacationTypeModel = {
/** /**
* 모든 활성 휴가 유형 조회 (우선순위 순서대로) * 모든 활성 휴가 유형 조회 (우선순위 순서대로)
*/ */
async getAll(callback) { async getAll() {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(`
const query = ` SELECT *
SELECT * FROM vacation_types
FROM vacation_types WHERE is_active = 1
WHERE is_active = 1 ORDER BY priority ASC, id ASC
ORDER BY priority ASC, id ASC `);
`; return rows;
const [rows] = await db.query(query);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 시스템 기본 휴가 유형만 조회 * 시스템 기본 휴가 유형만 조회
*/ */
async getSystemTypes(callback) { async getSystemTypes() {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(`
const query = ` SELECT *
SELECT * FROM vacation_types
FROM vacation_types WHERE is_system = 1 AND is_active = 1
WHERE is_system = 1 AND is_active = 1 ORDER BY priority ASC
ORDER BY priority ASC `);
`; return rows;
const [rows] = await db.query(query); },
callback(null, rows);
} catch (error) { /**
callback(error); * 특별 휴가 유형만 조회
} */
async getSpecialTypes() {
const db = await getDb();
const [rows] = await db.query(`
SELECT *
FROM vacation_types
WHERE is_special = 1 AND is_active = 1
ORDER BY priority ASC
`);
return rows;
}, },
/** /**
* 특정 ID로 휴가 유형 조회 * 특정 ID로 휴가 유형 조회
*/ */
async getById(id, callback) { async getById(id) {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(`SELECT * FROM vacation_types WHERE id = ?`, [id]);
const query = `SELECT * FROM vacation_types WHERE id = ?`; return rows;
const [rows] = await db.query(query, [id]);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 유형 코드로 조회 * 휴가 유형 코드로 조회
*/ */
async getByCode(code, callback) { async getByCode(code) {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(`SELECT * FROM vacation_types WHERE type_code = ?`, [code]);
const query = `SELECT * FROM vacation_types WHERE type_code = ?`; return rows;
const [rows] = await db.query(query, [code]);
callback(null, rows);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 유형 생성 * 휴가 유형 생성
*/ */
async create(typeData, callback) { async create(typeData) {
try { const db = await getDb();
const db = await getDb(); const [result] = await db.query(`INSERT INTO vacation_types SET ?`, typeData);
const query = `INSERT INTO vacation_types SET ?`; return result;
const [result] = await db.query(query, typeData);
callback(null, result);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 유형 수정 * 휴가 유형 수정
*/ */
async update(id, updateData, callback) { async update(id, updateData) {
try { const db = await getDb();
const db = await getDb(); const [result] = await db.query(`UPDATE vacation_types SET ? WHERE id = ?`, [updateData, id]);
const query = `UPDATE vacation_types SET ? WHERE id = ?`; return result;
const [result] = await db.query(query, [updateData, id]);
callback(null, result);
} catch (error) {
callback(error);
}
}, },
/** /**
* 휴가 유형 삭제 (논리적 삭제 - is_active = 0) * 휴가 유형 삭제 (논리적 삭제 - is_active = 0)
*/ */
async delete(id, callback) { async delete(id) {
try { const db = await getDb();
const db = await getDb(); const [result] = await db.query(`UPDATE vacation_types SET is_active = 0, updated_at = NOW() WHERE id = ?`, [id]);
const query = `UPDATE vacation_types SET is_active = 0, updated_at = NOW() WHERE id = ?`; return result;
const [result] = await db.query(query, [id]);
callback(null, result);
} catch (error) {
callback(error);
}
}, },
/** /**
* 우선순위 업데이트 * 우선순위 업데이트
*/ */
async updatePriority(id, priority, callback) { async updatePriority(id, priority) {
try { const db = await getDb();
const db = await getDb(); const [result] = await db.query(`UPDATE vacation_types SET priority = ?, updated_at = NOW() WHERE id = ?`, [priority, id]);
const query = `UPDATE vacation_types SET priority = ?, updated_at = NOW() WHERE id = ?`; return result;
const [result] = await db.query(query, [priority, id]);
callback(null, result);
} catch (error) {
callback(error);
}
} }
}; };

View File

@@ -2,484 +2,324 @@ const { getDb } = require('../dbPool');
// ==================== 출입 신청 관리 ==================== // ==================== 출입 신청 관리 ====================
/** const createVisitRequest = async (requestData) => {
* 출입 신청 생성 const db = await getDb();
*/ const {
const createVisitRequest = async (requestData, callback) => { requester_id,
try { visitor_company,
const db = await getDb(); visitor_count = 1,
const { category_id,
requester_id, workplace_id,
visitor_company, visit_date,
visitor_count = 1, visit_time,
category_id, purpose_id,
workplace_id, notes = null
visit_date, } = requestData;
visit_time,
purpose_id,
notes = null
} = requestData;
const [result] = await db.query( const [result] = await db.query(
`INSERT INTO workplace_visit_requests `INSERT INTO workplace_visit_requests
(requester_id, visitor_company, visitor_count, category_id, workplace_id, (requester_id, visitor_company, visitor_count, category_id, workplace_id,
visit_date, visit_time, purpose_id, notes) visit_date, visit_time, purpose_id, notes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[requester_id, visitor_company, visitor_count, category_id, workplace_id, [requester_id, visitor_company, visitor_count, category_id, workplace_id,
visit_date, visit_time, purpose_id, notes] visit_date, visit_time, purpose_id, notes]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** const getAllVisitRequests = async (filters = {}) => {
* 출입 신청 목록 조회 (필터 옵션 포함) const db = await getDb();
*/ let query = `
const getAllVisitRequests = async (filters = {}, callback) => { SELECT
try { vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count,
const db = await getDb(); vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time,
let query = ` vr.purpose_id, vr.notes, vr.status,
SELECT vr.approved_by, vr.approved_at, vr.rejection_reason,
vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count, vr.created_at, vr.updated_at,
vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time, u.username as requester_name, u.name as requester_full_name,
vr.purpose_id, vr.notes, vr.status, wc.category_name, w.workplace_name,
vr.approved_by, vr.approved_at, vr.rejection_reason, vpt.purpose_name,
vr.created_at, vr.updated_at, approver.username as approver_name
u.username as requester_name, u.name as requester_full_name, FROM workplace_visit_requests vr
wc.category_name, w.workplace_name, INNER JOIN users u ON vr.requester_id = u.user_id
vpt.purpose_name, INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id
approver.username as approver_name INNER JOIN workplaces w ON vr.workplace_id = w.workplace_id
FROM workplace_visit_requests vr INNER JOIN visit_purpose_types vpt ON vr.purpose_id = vpt.purpose_id
INNER JOIN users u ON vr.requester_id = u.user_id LEFT JOIN users approver ON vr.approved_by = approver.user_id
INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id WHERE 1=1
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 = []; const params = [];
// 필터 적용 if (filters.status) {
if (filters.status) { query += ` AND vr.status = ?`;
query += ` AND vr.status = ?`; params.push(filters.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);
} }
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);
return rows;
}; };
/** const getVisitRequestById = async (requestId) => {
* 출입 신청 상세 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getVisitRequestById = async (requestId, callback) => { `SELECT
try { vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count,
const db = await getDb(); vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time,
const [rows] = await db.query( vr.purpose_id, vr.notes, vr.status,
`SELECT vr.approved_by, vr.approved_at, vr.rejection_reason,
vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count, vr.created_at, vr.updated_at,
vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time, u.username as requester_name, u.name as requester_full_name,
vr.purpose_id, vr.notes, vr.status, wc.category_name, w.workplace_name,
vr.approved_by, vr.approved_at, vr.rejection_reason, vpt.purpose_name,
vr.created_at, vr.updated_at, approver.username as approver_name
u.username as requester_name, u.name as requester_full_name, FROM workplace_visit_requests vr
wc.category_name, w.workplace_name, INNER JOIN users u ON vr.requester_id = u.user_id
vpt.purpose_name, INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id
approver.username as approver_name INNER JOIN workplaces w ON vr.workplace_id = w.workplace_id
FROM workplace_visit_requests vr INNER JOIN visit_purpose_types vpt ON vr.purpose_id = vpt.purpose_id
INNER JOIN users u ON vr.requester_id = u.user_id LEFT JOIN users approver ON vr.approved_by = approver.user_id
INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id WHERE vr.request_id = ?`,
INNER JOIN workplaces w ON vr.workplace_id = w.workplace_id [requestId]
INNER JOIN visit_purpose_types vpt ON vr.purpose_id = vpt.purpose_id );
LEFT JOIN users approver ON vr.approved_by = approver.user_id return rows[0];
WHERE vr.request_id = ?`,
[requestId]
);
callback(null, rows[0]);
} catch (err) {
callback(err);
}
}; };
/** const updateVisitRequest = async (requestId, requestData) => {
* 출입 신청 수정 const db = await getDb();
*/ const {
const updateVisitRequest = async (requestId, requestData, callback) => { visitor_company, visitor_count, category_id, workplace_id,
try { visit_date, visit_time, purpose_id, notes
const db = await getDb(); } = requestData;
const {
visitor_company,
visitor_count,
category_id,
workplace_id,
visit_date,
visit_time,
purpose_id,
notes
} = requestData;
const [result] = await db.query( const [result] = await db.query(
`UPDATE workplace_visit_requests `UPDATE workplace_visit_requests
SET visitor_company = ?, visitor_count = ?, category_id = ?, workplace_id = ?, SET visitor_company = ?, visitor_count = ?, category_id = ?, workplace_id = ?,
visit_date = ?, visit_time = ?, purpose_id = ?, notes = ?, updated_at = NOW() visit_date = ?, visit_time = ?, purpose_id = ?, notes = ?, updated_at = NOW()
WHERE request_id = ?`, WHERE request_id = ?`,
[visitor_company, visitor_count, category_id, workplace_id, [visitor_company, visitor_count, category_id, workplace_id,
visit_date, visit_time, purpose_id, notes, requestId] visit_date, visit_time, purpose_id, notes, requestId]
); );
return result;
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const deleteVisitRequest = async (requestId) => {
* 출입 신청 삭제 const db = await getDb();
*/ const [result] = await db.query(
const deleteVisitRequest = async (requestId, callback) => { `DELETE FROM workplace_visit_requests WHERE request_id = ?`,
try { [requestId]
const db = await getDb(); );
const [result] = await db.query( return result;
`DELETE FROM workplace_visit_requests WHERE request_id = ?`,
[requestId]
);
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const approveVisitRequest = async (requestId, approvedBy) => {
* 출입 신청 승인 const db = await getDb();
*/ const [result] = await db.query(
const approveVisitRequest = async (requestId, approvedBy, callback) => { `UPDATE workplace_visit_requests
try { SET status = 'approved', approved_by = ?, approved_at = NOW(), updated_at = NOW()
const db = await getDb(); WHERE request_id = ?`,
const [result] = await db.query( [approvedBy, requestId]
`UPDATE workplace_visit_requests );
SET status = 'approved', approved_by = ?, approved_at = NOW(), updated_at = NOW() return result;
WHERE request_id = ?`,
[approvedBy, requestId]
);
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const rejectVisitRequest = async (requestId, rejectionData) => {
* 출입 신청 반려 const db = await getDb();
*/ const { approved_by, rejection_reason } = rejectionData;
const rejectVisitRequest = async (requestId, rejectionData, callback) => {
try {
const db = await getDb();
const { approved_by, rejection_reason } = rejectionData;
const [result] = await db.query( const [result] = await db.query(
`UPDATE workplace_visit_requests `UPDATE workplace_visit_requests
SET status = 'rejected', approved_by = ?, approved_at = NOW(), SET status = 'rejected', approved_by = ?, approved_at = NOW(),
rejection_reason = ?, updated_at = NOW() rejection_reason = ?, updated_at = NOW()
WHERE request_id = ?`, WHERE request_id = ?`,
[approved_by, rejection_reason, requestId] [approved_by, rejection_reason, requestId]
); );
return result;
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const updateVisitRequestStatus = async (requestId, status) => {
* 출입 신청 상태 변경 const db = await getDb();
*/ const [result] = await db.query(
const updateVisitRequestStatus = async (requestId, status, callback) => { `UPDATE workplace_visit_requests
try { SET status = ?, updated_at = NOW()
const db = await getDb(); WHERE request_id = ?`,
const [result] = await db.query( [status, requestId]
`UPDATE workplace_visit_requests );
SET status = ?, updated_at = NOW() return result;
WHERE request_id = ?`,
[status, requestId]
);
callback(null, result);
} catch (err) {
callback(err);
}
}; };
// ==================== 방문 목적 관리 ==================== // ==================== 방문 목적 관리 ====================
/** const getAllVisitPurposes = async () => {
* 모든 방문 목적 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getAllVisitPurposes = async (callback) => { `SELECT purpose_id, purpose_name, display_order, is_active, created_at
try { FROM visit_purpose_types
const db = await getDb(); ORDER BY display_order ASC, purpose_id ASC`
const [rows] = await db.query( );
`SELECT purpose_id, purpose_name, display_order, is_active, created_at return rows;
FROM visit_purpose_types
ORDER BY display_order ASC, purpose_id ASC`
);
callback(null, rows);
} catch (err) {
callback(err);
}
}; };
/** const getActiveVisitPurposes = async () => {
* 활성 방문 목적만 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getActiveVisitPurposes = async (callback) => { `SELECT purpose_id, purpose_name, display_order, is_active, created_at
try { FROM visit_purpose_types
const db = await getDb(); WHERE is_active = TRUE
const [rows] = await db.query( ORDER BY display_order ASC, purpose_id ASC`
`SELECT purpose_id, purpose_name, display_order, is_active, created_at );
FROM visit_purpose_types return rows;
WHERE is_active = TRUE
ORDER BY display_order ASC, purpose_id ASC`
);
callback(null, rows);
} catch (err) {
callback(err);
}
}; };
/** const createVisitPurpose = async (purposeData) => {
* 방문 목적 추가 const db = await getDb();
*/ const { purpose_name, display_order = 0, is_active = true } = purposeData;
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( const [result] = await db.query(
`INSERT INTO visit_purpose_types (purpose_name, display_order, is_active) `INSERT INTO visit_purpose_types (purpose_name, display_order, is_active)
VALUES (?, ?, ?)`, VALUES (?, ?, ?)`,
[purpose_name, display_order, is_active] [purpose_name, display_order, is_active]
); );
return result.insertId;
callback(null, result.insertId);
} catch (err) {
callback(err);
}
}; };
/** const updateVisitPurpose = async (purposeId, purposeData) => {
* 방문 목적 수정 const db = await getDb();
*/ const { purpose_name, display_order, is_active } = purposeData;
const updateVisitPurpose = async (purposeId, purposeData, callback) => {
try {
const db = await getDb();
const { purpose_name, display_order, is_active } = purposeData;
const [result] = await db.query( const [result] = await db.query(
`UPDATE visit_purpose_types `UPDATE visit_purpose_types
SET purpose_name = ?, display_order = ?, is_active = ? SET purpose_name = ?, display_order = ?, is_active = ?
WHERE purpose_id = ?`, WHERE purpose_id = ?`,
[purpose_name, display_order, is_active, purposeId] [purpose_name, display_order, is_active, purposeId]
); );
return result;
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const deleteVisitPurpose = async (purposeId) => {
* 방문 목적 삭제 const db = await getDb();
*/ const [result] = await db.query(
const deleteVisitPurpose = async (purposeId, callback) => { `DELETE FROM visit_purpose_types WHERE purpose_id = ?`,
try { [purposeId]
const db = await getDb(); );
const [result] = await db.query( return result;
`DELETE FROM visit_purpose_types WHERE purpose_id = ?`,
[purposeId]
);
callback(null, result);
} catch (err) {
callback(err);
}
}; };
// ==================== 안전교육 기록 관리 ==================== // ==================== 안전교육 기록 관리 ====================
/** const createTrainingRecord = async (trainingData) => {
* 안전교육 기록 생성 const db = await getDb();
*/ const {
const createTrainingRecord = async (trainingData, callback) => { request_id, trainer_id, training_date,
try { training_start_time, training_end_time = null, training_topics = null
const db = await getDb(); } = trainingData;
const {
request_id,
trainer_id,
training_date,
training_start_time,
training_end_time = null,
training_topics = null
} = trainingData;
const [result] = await db.query( const [result] = await db.query(
`INSERT INTO safety_training_records `INSERT INTO safety_training_records
(request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics) (request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics)
VALUES (?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?)`,
[request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics] [request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics]
); );
return result.insertId;
callback(null, result.insertId);
} catch (err) {
callback(err);
}
}; };
/** const getTrainingRecordByRequestId = async (requestId) => {
* 특정 출입 신청의 안전교육 기록 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getTrainingRecordByRequestId = async (requestId, callback) => { `SELECT
try { str.training_id, str.request_id, str.trainer_id, str.training_date,
const db = await getDb(); str.training_start_time, str.training_end_time, str.training_topics,
const [rows] = await db.query( str.signature_data, str.completed_at, str.created_at, str.updated_at,
`SELECT u.username as trainer_name, u.name as trainer_full_name
str.training_id, str.request_id, str.trainer_id, str.training_date, FROM safety_training_records str
str.training_start_time, str.training_end_time, str.training_topics, INNER JOIN users u ON str.trainer_id = u.user_id
str.signature_data, str.completed_at, str.created_at, str.updated_at, WHERE str.request_id = ?`,
u.username as trainer_name, u.name as trainer_full_name [requestId]
FROM safety_training_records str );
INNER JOIN users u ON str.trainer_id = u.user_id return rows[0];
WHERE str.request_id = ?`,
[requestId]
);
callback(null, rows[0]);
} catch (err) {
callback(err);
}
}; };
/** const updateTrainingRecord = async (trainingId, trainingData) => {
* 안전교육 기록 수정 const db = await getDb();
*/ const { training_date, training_start_time, training_end_time, training_topics } = trainingData;
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( const [result] = await db.query(
`UPDATE safety_training_records `UPDATE safety_training_records
SET training_date = ?, training_start_time = ?, training_end_time = ?, SET training_date = ?, training_start_time = ?, training_end_time = ?,
training_topics = ?, updated_at = NOW() training_topics = ?, updated_at = NOW()
WHERE training_id = ?`, WHERE training_id = ?`,
[training_date, training_start_time, training_end_time, training_topics, trainingId] [training_date, training_start_time, training_end_time, training_topics, trainingId]
); );
return result;
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const completeTraining = async (trainingId, signatureData) => {
* 안전교육 완료 (서명 포함) const db = await getDb();
*/ const [result] = await db.query(
const completeTraining = async (trainingId, signatureData, callback) => { `UPDATE safety_training_records
try { SET signature_data = ?, completed_at = NOW(), updated_at = NOW()
const db = await getDb(); WHERE training_id = ?`,
const [result] = await db.query( [signatureData, trainingId]
`UPDATE safety_training_records );
SET signature_data = ?, completed_at = NOW(), updated_at = NOW() return result;
WHERE training_id = ?`,
[signatureData, trainingId]
);
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const getTrainingRecords = async (filters = {}) => {
* 안전교육 목록 조회 (날짜별 필터) const db = await getDb();
*/ let query = `
const getTrainingRecords = async (filters = {}, callback) => { SELECT
try { str.training_id, str.request_id, str.trainer_id, str.training_date,
const db = await getDb(); str.training_start_time, str.training_end_time, str.training_topics,
let query = ` str.completed_at, str.created_at, str.updated_at,
SELECT u.username as trainer_name, u.name as trainer_full_name,
str.training_id, str.request_id, str.trainer_id, str.training_date, vr.visitor_company, vr.visitor_count, vr.visit_date
str.training_start_time, str.training_end_time, str.training_topics, FROM safety_training_records str
str.completed_at, str.created_at, str.updated_at, INNER JOIN users u ON str.trainer_id = u.user_id
u.username as trainer_name, u.name as trainer_full_name, INNER JOIN workplace_visit_requests vr ON str.request_id = vr.request_id
vr.visitor_company, vr.visitor_count, vr.visit_date WHERE 1=1
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 = []; const params = [];
if (filters.training_date) { if (filters.training_date) {
query += ` AND str.training_date = ?`; query += ` AND str.training_date = ?`;
params.push(filters.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);
} }
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);
return rows;
}; };
module.exports = { module.exports = {
// 출입 신청
createVisitRequest, createVisitRequest,
getAllVisitRequests, getAllVisitRequests,
getVisitRequestById, getVisitRequestById,
@@ -488,15 +328,11 @@ module.exports = {
approveVisitRequest, approveVisitRequest,
rejectVisitRequest, rejectVisitRequest,
updateVisitRequestStatus, updateVisitRequestStatus,
// 방문 목적
getAllVisitPurposes, getAllVisitPurposes,
getActiveVisitPurposes, getActiveVisitPurposes,
createVisitPurpose, createVisitPurpose,
updateVisitPurpose, updateVisitPurpose,
deleteVisitPurpose, deleteVisitPurpose,
// 안전교육
createTrainingRecord, createTrainingRecord,
getTrainingRecordByRequestId, getTrainingRecordByRequestId,
updateTrainingRecord, updateTrainingRecord,

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ const { getDb } = require('../dbPool');
/** /**
* 1. 여러 건 등록 (트랜잭션 사용) * 1. 여러 건 등록 (트랜잭션 사용)
*/ */
const createBatch = async (reports, callback) => { const createBatch = async (reports) => {
const db = await getDb(); const db = await getDb();
const conn = await db.getConnection(); const conn = await db.getConnection();
@@ -30,10 +30,9 @@ const createBatch = async (reports, callback) => {
} }
await conn.commit(); await conn.commit();
callback(null);
} catch (err) { } catch (err) {
await conn.rollback(); await conn.rollback();
callback(err); throw err;
} finally { } finally {
conn.release(); conn.release();
} }
@@ -42,175 +41,146 @@ const createBatch = async (reports, callback) => {
/** /**
* 2. 단일 등록 * 2. 단일 등록
*/ */
const create = async (report, callback) => { const create = async (report) => {
try { const db = await getDb();
const db = await getDb(); const {
const { date, worker_id, project_id,
date, worker_id, project_id, task_id, overtime_hours,
task_id, overtime_hours, work_details, memo
work_details, memo } = report;
} = report;
const [result] = await db.query( const [result] = await db.query(
`INSERT INTO WorkReports `INSERT INTO WorkReports
(\`date\`, worker_id, project_id, task_id, overtime_hours, work_details, memo) (\`date\`, worker_id, project_id, task_id, overtime_hours, work_details, memo)
VALUES (?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?)`,
[ [
date, date,
worker_id, worker_id,
project_id, project_id,
task_id || null, task_id || null,
overtime_hours || null, overtime_hours || null,
work_details || null, work_details || null,
memo || null memo || null
] ]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** /**
* 3. 날짜별 조회 * 3. 날짜별 조회
*/ */
const getAllByDate = async (date, callback) => { const getAllByDate = async (date) => {
try { const db = await getDb();
const db = await getDb(); const sql = `
const sql = ` SELECT
SELECT wr.worker_id,
wr.worker_id, -- 이 줄을 추가했습니다 wr.id,
wr.id, wr.\`date\`,
wr.\`date\`, w.worker_name,
w.worker_name, p.project_name,
p.project_name, CONCAT(t.category, ':', t.subcategory) AS task_name,
CONCAT(t.category, ':', t.subcategory) AS task_name, wr.overtime_hours,
wr.overtime_hours, wr.work_details,
wr.work_details, wr.memo
wr.memo FROM WorkReports wr
FROM WorkReports wr LEFT JOIN workers w ON wr.worker_id = w.worker_id
LEFT JOIN workers w ON wr.worker_id = w.worker_id LEFT JOIN projects p ON wr.project_id = p.project_id
LEFT JOIN projects p ON wr.project_id = p.project_id LEFT JOIN Tasks t ON wr.task_id = t.task_id
LEFT JOIN Tasks t ON wr.task_id = t.task_id WHERE wr.\`date\` = ?
WHERE wr.\`date\` = ? ORDER BY w.worker_name ASC
ORDER BY w.worker_name ASC `;
`; const [rows] = await db.query(sql, [date]);
const [rows] = await db.query(sql, [date]); return rows;
callback(null, rows); };
} catch (err) {
callback(err);
}
};
/** /**
* 4. 기간 조회 * 4. 기간 조회
*/ */
const getByRange = async (start, end, callback) => { const getByRange = async (start, end) => {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(
const [rows] = await db.query( `SELECT id, \`date\`, worker_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports
`SELECT id, \`date\`, worker_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports WHERE \`date\` BETWEEN ? AND ?
WHERE \`date\` BETWEEN ? AND ? ORDER BY \`date\` ASC`,
ORDER BY \`date\` ASC`, [start, end]
[start, end] );
); return rows;
callback(null, rows);
} catch (err) {
callback(err);
}
}; };
/** /**
* 5. ID로 조회 * 5. ID로 조회
*/ */
const getById = async (id, callback) => { const getById = async (id) => {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(
const [rows] = await db.query( `SELECT id, \`date\`, worker_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports WHERE id = ?`,
`SELECT id, \`date\`, worker_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports WHERE id = ?`, [id]
[id] );
); return rows[0];
callback(null, rows[0]);
} catch (err) {
callback(err);
}
}; };
/** /**
* 6. 수정 * 6. 수정
*/ */
const update = async (id, report, callback) => { const update = async (id, report) => {
try { const db = await getDb();
const db = await getDb(); const {
const { date, worker_id, project_id,
date, worker_id, project_id, task_id, overtime_hours,
task_id, overtime_hours, work_details, memo
work_details, memo } = report;
} = report;
const [result] = await db.query( const [result] = await db.query(
`UPDATE WorkReports `UPDATE WorkReports
SET \`date\` = ?, SET \`date\` = ?,
worker_id = ?, worker_id = ?,
project_id = ?, project_id = ?,
task_id = ?, task_id = ?,
overtime_hours = ?, overtime_hours = ?,
work_details = ?, work_details = ?,
memo = ?, memo = ?,
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
WHERE id = ?`, WHERE id = ?`,
[ [
date, date,
worker_id, worker_id,
project_id, project_id,
task_id || null, task_id || null,
overtime_hours || null, overtime_hours || null,
work_details || null, work_details || null,
memo || null, memo || null,
id id
] ]
); );
callback(null, result.affectedRows); return result.affectedRows;
} catch (err) {
callback(err);
}
}; };
/** /**
* 7. 삭제 * 7. 삭제
*/ */
const remove = async (id, callback) => { const remove = async (id) => {
try { const db = await getDb();
const db = await getDb(); const [result] = await db.query(
const [result] = await db.query( `DELETE FROM WorkReports WHERE id = ?`,
`DELETE FROM WorkReports WHERE id = ?`, [id]
[id] );
); return result.affectedRows;
callback(null, result.affectedRows);
} catch (err) {
callback(new Error(err.message || String(err)));
}
}; };
/** /**
* 8. 중복 확인 * 8. 중복 확인
*/ */
const existsByDateAndWorker = async (date, worker_id, callback) => { const existsByDateAndWorker = async (date, worker_id) => {
try { const db = await getDb();
const db = await getDb(); const [rows] = await db.query(
const [rows] = await db.query( `SELECT 1 FROM WorkReports WHERE \`date\` = ? AND worker_id = ? LIMIT 1`,
`SELECT 1 FROM WorkReports WHERE \`date\` = ? AND worker_id = ? LIMIT 1`, [date, worker_id]
[date, worker_id] );
); return rows.length > 0;
callback(null, rows.length > 0);
} catch (err) {
callback(err);
}
}; };
// ✅ 내보내기
module.exports = { module.exports = {
create, create,
createBatch, createBatch,
@@ -220,4 +190,4 @@ module.exports = {
update, update,
remove, remove,
existsByDateAndWorker existsByDateAndWorker
}; };

View File

@@ -2,426 +2,290 @@ const { getDb } = require('../dbPool');
// ==================== 카테고리(공장) 관련 ==================== // ==================== 카테고리(공장) 관련 ====================
/** const createCategory = async (category) => {
* 카테고리 생성 const db = await getDb();
*/ const {
const createCategory = async (category, callback) => { category_name,
try { description = null,
const db = await getDb(); display_order = 0,
const { is_active = true
category_name, } = category;
description = null,
display_order = 0,
is_active = true
} = category;
const [result] = await db.query( const [result] = await db.query(
`INSERT INTO workplace_categories `INSERT INTO workplace_categories
(category_name, description, display_order, is_active) (category_name, description, display_order, is_active)
VALUES (?, ?, ?, ?)`, VALUES (?, ?, ?, ?)`,
[category_name, description, display_order, is_active] [category_name, description, display_order, is_active]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** const getAllCategories = async () => {
* 모든 카테고리 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getAllCategories = async (callback) => { `SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
try { FROM workplace_categories
const db = await getDb(); ORDER BY display_order ASC, category_id ASC`
const [rows] = await db.query( );
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at return rows;
FROM workplace_categories
ORDER BY display_order ASC, category_id ASC`
);
callback(null, rows);
} catch (err) {
callback(err);
}
}; };
/** const getActiveCategories = async () => {
* 활성 카테고리만 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getActiveCategories = async (callback) => { `SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
try { FROM workplace_categories
const db = await getDb(); WHERE is_active = TRUE
const [rows] = await db.query( ORDER BY display_order ASC, category_id ASC`
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at );
FROM workplace_categories return rows;
WHERE is_active = TRUE
ORDER BY display_order ASC, category_id ASC`
);
callback(null, rows);
} catch (err) {
callback(err);
}
}; };
/** const getCategoryById = async (categoryId) => {
* ID로 카테고리 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getCategoryById = async (categoryId, callback) => { `SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
try { FROM workplace_categories
const db = await getDb(); WHERE category_id = ?`,
const [rows] = await db.query( [categoryId]
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at );
FROM workplace_categories return rows[0];
WHERE category_id = ?`,
[categoryId]
);
callback(null, rows[0]);
} catch (err) {
callback(err);
}
}; };
/** const updateCategory = async (categoryId, category) => {
* 카테고리 수정 const db = await getDb();
*/ const {
const updateCategory = async (categoryId, category, callback) => { category_name,
try { description,
const db = await getDb(); display_order,
const { is_active,
category_name, layout_image
description, } = category;
display_order,
is_active,
layout_image
} = category;
const [result] = await db.query( const [result] = await db.query(
`UPDATE workplace_categories `UPDATE workplace_categories
SET category_name = ?, description = ?, display_order = ?, is_active = ?, layout_image = ?, updated_at = NOW() SET category_name = ?, description = ?, display_order = ?, is_active = ?, layout_image = ?, updated_at = NOW()
WHERE category_id = ?`, WHERE category_id = ?`,
[category_name, description, display_order, is_active, layout_image, categoryId] [category_name, description, display_order, is_active, layout_image, categoryId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const deleteCategory = async (categoryId) => {
* 카테고리 삭제 const db = await getDb();
*/ const [result] = await db.query(
const deleteCategory = async (categoryId, callback) => { `DELETE FROM workplace_categories WHERE category_id = ?`,
try { [categoryId]
const db = await getDb(); );
const [result] = await db.query( return result;
`DELETE FROM workplace_categories WHERE category_id = ?`,
[categoryId]
);
callback(null, result);
} catch (err) {
callback(err);
}
}; };
// ==================== 작업장 관련 ==================== // ==================== 작업장 관련 ====================
/** const createWorkplace = async (workplace) => {
* 작업장 생성 const db = await getDb();
*/ const {
const createWorkplace = async (workplace, callback) => { category_id = null,
try { workplace_name,
const db = await getDb(); description = null,
const { is_active = true,
category_id = null, workplace_purpose = null,
workplace_name, display_priority = 0
description = null, } = workplace;
is_active = true,
workplace_purpose = null,
display_priority = 0
} = workplace;
const [result] = await db.query( const [result] = await db.query(
`INSERT INTO workplaces `INSERT INTO workplaces
(category_id, workplace_name, description, is_active, workplace_purpose, display_priority) (category_id, workplace_name, description, is_active, workplace_purpose, display_priority)
VALUES (?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?)`,
[category_id, workplace_name, description, is_active, workplace_purpose, display_priority] [category_id, workplace_name, description, is_active, workplace_purpose, display_priority]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** const getAllWorkplaces = async () => {
* 모든 작업장 조회 (카테고리 정보 포함) const db = await getDb();
*/ const [rows] = await db.query(
const getAllWorkplaces = async (callback) => { `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
try { w.layout_image, w.created_at, w.updated_at,
const db = await getDb(); wc.category_name
const [rows] = await db.query( FROM workplaces w
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority, LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
w.layout_image, w.created_at, w.updated_at, ORDER BY wc.display_order ASC, w.display_priority ASC, w.workplace_id DESC`
wc.category_name );
FROM workplaces w return rows;
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
ORDER BY wc.display_order ASC, w.display_priority ASC, w.workplace_id DESC`
);
callback(null, rows);
} catch (err) {
callback(err);
}
}; };
/** const getActiveWorkplaces = async () => {
* 활성 작업장만 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getActiveWorkplaces = async (callback) => { `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
try { w.layout_image, w.created_at, w.updated_at,
const db = await getDb(); wc.category_name
const [rows] = await db.query( FROM workplaces w
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority, LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
w.layout_image, w.created_at, w.updated_at, WHERE w.is_active = TRUE
wc.category_name ORDER BY wc.display_order ASC, w.workplace_id DESC`
FROM workplaces w );
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id return rows;
WHERE w.is_active = TRUE
ORDER BY wc.display_order ASC, w.workplace_id DESC`
);
callback(null, rows);
} catch (err) {
callback(err);
}
}; };
/** const getWorkplacesByCategory = async (categoryId) => {
* 카테고리별 작업장 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getWorkplacesByCategory = async (categoryId, callback) => { `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
try { w.layout_image, w.created_at, w.updated_at,
const db = await getDb(); wc.category_name
const [rows] = await db.query( FROM workplaces w
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority, LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
w.layout_image, w.created_at, w.updated_at, WHERE w.category_id = ?
wc.category_name ORDER BY w.workplace_id DESC`,
FROM workplaces w [categoryId]
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id );
WHERE w.category_id = ? return rows;
ORDER BY w.workplace_id DESC`,
[categoryId]
);
callback(null, rows);
} catch (err) {
callback(err);
}
}; };
/** const getWorkplaceById = async (workplaceId) => {
* ID로 작업장 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getWorkplaceById = async (workplaceId, callback) => { `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
try { w.layout_image, w.created_at, w.updated_at,
const db = await getDb(); wc.category_name
const [rows] = await db.query( FROM workplaces w
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority, LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
w.layout_image, w.created_at, w.updated_at, WHERE w.workplace_id = ?`,
wc.category_name [workplaceId]
FROM workplaces w );
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id return rows[0];
WHERE w.workplace_id = ?`,
[workplaceId]
);
callback(null, rows[0]);
} catch (err) {
callback(err);
}
}; };
/** const updateWorkplace = async (workplaceId, workplace) => {
* 작업장 수정 const db = await getDb();
*/ const {
const updateWorkplace = async (workplaceId, workplace, callback) => { category_id,
try { workplace_name,
const db = await getDb(); description,
const { is_active,
category_id, workplace_purpose,
workplace_name, display_priority,
description, layout_image
is_active, } = workplace;
workplace_purpose,
display_priority,
layout_image
} = workplace;
const [result] = await db.query( const [result] = await db.query(
`UPDATE workplaces `UPDATE workplaces
SET category_id = ?, workplace_name = ?, description = ?, is_active = ?, SET category_id = ?, workplace_name = ?, description = ?, is_active = ?,
workplace_purpose = ?, display_priority = ?, layout_image = ?, updated_at = NOW() workplace_purpose = ?, display_priority = ?, layout_image = ?, updated_at = NOW()
WHERE workplace_id = ?`, WHERE workplace_id = ?`,
[category_id, workplace_name, description, is_active, workplace_purpose, display_priority, layout_image, workplaceId] [category_id, workplace_name, description, is_active, workplace_purpose, display_priority, layout_image, workplaceId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const deleteWorkplace = async (workplaceId) => {
* 작업장 삭제 const db = await getDb();
*/ const [result] = await db.query(
const deleteWorkplace = async (workplaceId, callback) => { `DELETE FROM workplaces WHERE workplace_id = ?`,
try { [workplaceId]
const db = await getDb(); );
const [result] = await db.query( return result;
`DELETE FROM workplaces WHERE workplace_id = ?`,
[workplaceId]
);
callback(null, result);
} catch (err) {
callback(err);
}
}; };
// ==================== 작업장 지도 영역 관련 ==================== // ==================== 작업장 지도 영역 관련 ====================
/** const createMapRegion = async (region) => {
* 작업장 지도 영역 생성 const db = await getDb();
*/ const {
const createMapRegion = async (region, callback) => { workplace_id,
try { category_id,
const db = await getDb(); x_start,
const { y_start,
workplace_id, x_end,
category_id, y_end,
x_start, shape = 'rect',
y_start, polygon_points = null
x_end, } = region;
y_end,
shape = 'rect',
polygon_points = null
} = region;
const [result] = await db.query( const [result] = await db.query(
`INSERT INTO workplace_map_regions `INSERT INTO workplace_map_regions
(workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points) (workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points] [workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points]
); );
callback(null, result.insertId); return result.insertId;
} catch (err) {
callback(err);
}
}; };
/** const getMapRegionsByCategory = async (categoryId) => {
* 카테고리(공장)별 지도 영역 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getMapRegionsByCategory = async (categoryId, callback) => { `SELECT mr.*, w.workplace_name, w.description
try { FROM workplace_map_regions mr
const db = await getDb(); INNER JOIN workplaces w ON mr.workplace_id = w.workplace_id
const [rows] = await db.query( WHERE mr.category_id = ? AND w.is_active = TRUE
`SELECT mr.*, w.workplace_name, w.description ORDER BY mr.region_id ASC`,
FROM workplace_map_regions mr [categoryId]
INNER JOIN workplaces w ON mr.workplace_id = w.workplace_id );
WHERE mr.category_id = ? AND w.is_active = TRUE return rows;
ORDER BY mr.region_id ASC`,
[categoryId]
);
callback(null, rows);
} catch (err) {
callback(err);
}
}; };
/** const getMapRegionByWorkplace = async (workplaceId) => {
* 작업장별 지도 영역 조회 const db = await getDb();
*/ const [rows] = await db.query(
const getMapRegionByWorkplace = async (workplaceId, callback) => { `SELECT * FROM workplace_map_regions WHERE workplace_id = ?`,
try { [workplaceId]
const db = await getDb(); );
const [rows] = await db.query( return rows[0];
`SELECT * FROM workplace_map_regions WHERE workplace_id = ?`,
[workplaceId]
);
callback(null, rows[0]);
} catch (err) {
callback(err);
}
}; };
/** const updateMapRegion = async (regionId, region) => {
* 지도 영역 수정 const db = await getDb();
*/ const {
const updateMapRegion = async (regionId, region, callback) => { x_start,
try { y_start,
const db = await getDb(); x_end,
const { y_end,
x_start, shape,
y_start, polygon_points
x_end, } = region;
y_end,
shape,
polygon_points
} = region;
const [result] = await db.query( const [result] = await db.query(
`UPDATE workplace_map_regions `UPDATE workplace_map_regions
SET x_start = ?, y_start = ?, x_end = ?, y_end = ?, shape = ?, polygon_points = ?, updated_at = NOW() SET x_start = ?, y_start = ?, x_end = ?, y_end = ?, shape = ?, polygon_points = ?, updated_at = NOW()
WHERE region_id = ?`, WHERE region_id = ?`,
[x_start, y_start, x_end, y_end, shape, polygon_points, regionId] [x_start, y_start, x_end, y_end, shape, polygon_points, regionId]
); );
callback(null, result); return result;
} catch (err) {
callback(err);
}
}; };
/** const deleteMapRegion = async (regionId) => {
* 지도 영역 삭제 const db = await getDb();
*/ const [result] = await db.query(
const deleteMapRegion = async (regionId, callback) => { `DELETE FROM workplace_map_regions WHERE region_id = ?`,
try { [regionId]
const db = await getDb(); );
const [result] = await db.query( return result;
`DELETE FROM workplace_map_regions WHERE region_id = ?`,
[regionId]
);
callback(null, result);
} catch (err) {
callback(err);
}
}; };
/** const deleteMapRegionsByCategory = async (categoryId) => {
* 작업장 영역 일괄 삭제 (카테고리별) const db = await getDb();
*/ const [result] = await db.query(
const deleteMapRegionsByCategory = async (categoryId, callback) => { `DELETE FROM workplace_map_regions WHERE category_id = ?`,
try { [categoryId]
const db = await getDb(); );
const [result] = await db.query( return result;
`DELETE FROM workplace_map_regions WHERE category_id = ?`,
[categoryId]
);
callback(null, result);
} catch (err) {
callback(err);
}
}; };
module.exports = { module.exports = {
// 카테고리
createCategory, createCategory,
getAllCategories, getAllCategories,
getActiveCategories, getActiveCategories,
getCategoryById, getCategoryById,
updateCategory, updateCategory,
deleteCategory, deleteCategory,
// 작업장
createWorkplace, createWorkplace,
getAllWorkplaces, getAllWorkplaces,
getActiveWorkplaces, getActiveWorkplaces,
@@ -429,8 +293,6 @@ module.exports = {
getWorkplaceById, getWorkplaceById,
updateWorkplace, updateWorkplace,
deleteWorkplace, deleteWorkplace,
// 지도 영역
createMapRegion, createMapRegion,
getMapRegionsByCategory, getMapRegionsByCategory,
getMapRegionByWorkplace, getMapRegionByWorkplace,

View File

@@ -24,16 +24,10 @@ const createWorkReportService = async (reportData) => {
logger.info('작업 보고서 생성 요청', { count: reports.length }); logger.info('작업 보고서 생성 요청', { count: reports.length });
const workReport_ids = [];
try { try {
const workReport_ids = [];
for (const report of reports) { for (const report of reports) {
const id = await new Promise((resolve, reject) => { const id = await workReportModel.create(report);
workReportModel.create(report, (err, insertId) => {
if (err) reject(err);
else resolve(insertId);
});
});
workReport_ids.push(id); workReport_ids.push(id);
} }
@@ -66,15 +60,8 @@ const getWorkReportsByDateService = async (date) => {
logger.info('작업 보고서 날짜별 조회 요청', { date }); logger.info('작업 보고서 날짜별 조회 요청', { date });
try { try {
const rows = await new Promise((resolve, reject) => { const rows = await workReportModel.getAllByDate(date);
workReportModel.getAllByDate(date, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
logger.info('작업 보고서 조회 성공', { date, count: rows.length }); logger.info('작업 보고서 조회 성공', { date, count: rows.length });
return rows; return rows;
} catch (error) { } catch (error) {
logger.error('작업 보고서 조회 실패', { date, error: error.message }); logger.error('작업 보고서 조회 실패', { date, error: error.message });
@@ -96,15 +83,8 @@ const getWorkReportsInRangeService = async (start, end) => {
logger.info('작업 보고서 기간별 조회 요청', { start, end }); logger.info('작업 보고서 기간별 조회 요청', { start, end });
try { try {
const rows = await new Promise((resolve, reject) => { const rows = await workReportModel.getByRange(start, end);
workReportModel.getByRange(start, end, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
logger.info('작업 보고서 조회 성공', { start, end, count: rows.length }); logger.info('작업 보고서 조회 성공', { start, end, count: rows.length });
return rows; return rows;
} catch (error) { } catch (error) {
logger.error('작업 보고서 조회 실패', { start, end, error: error.message }); logger.error('작업 보고서 조회 실패', { start, end, error: error.message });
@@ -123,12 +103,7 @@ const getWorkReportByIdService = async (id) => {
logger.info('작업 보고서 조회 요청', { report_id: id }); logger.info('작업 보고서 조회 요청', { report_id: id });
try { try {
const row = await new Promise((resolve, reject) => { const row = await workReportModel.getById(id);
workReportModel.getById(id, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (!row) { if (!row) {
logger.warn('작업 보고서를 찾을 수 없음', { report_id: id }); logger.warn('작업 보고서를 찾을 수 없음', { report_id: id });
@@ -136,13 +111,9 @@ const getWorkReportByIdService = async (id) => {
} }
logger.info('작업 보고서 조회 성공', { report_id: id }); logger.info('작업 보고서 조회 성공', { report_id: id });
return row; return row;
} catch (error) { } catch (error) {
if (error instanceof NotFoundError) { if (error instanceof NotFoundError) throw error;
throw error;
}
logger.error('작업 보고서 조회 실패', { report_id: id, error: error.message }); logger.error('작업 보고서 조회 실패', { report_id: id, error: error.message });
throw new DatabaseError('작업 보고서 조회 중 오류가 발생했습니다'); throw new DatabaseError('작업 보고서 조회 중 오류가 발생했습니다');
} }
@@ -159,12 +130,7 @@ const updateWorkReportService = async (id, updateData) => {
logger.info('작업 보고서 수정 요청', { report_id: id }); logger.info('작업 보고서 수정 요청', { report_id: id });
try { try {
const changes = await new Promise((resolve, reject) => { const changes = await workReportModel.update(id, updateData);
workReportModel.update(id, updateData, (err, affectedRows) => {
if (err) reject(err);
else resolve(affectedRows);
});
});
if (changes === 0) { if (changes === 0) {
logger.warn('작업 보고서를 찾을 수 없거나 변경사항 없음', { report_id: id }); logger.warn('작업 보고서를 찾을 수 없거나 변경사항 없음', { report_id: id });
@@ -172,13 +138,9 @@ const updateWorkReportService = async (id, updateData) => {
} }
logger.info('작업 보고서 수정 성공', { report_id: id, changes }); logger.info('작업 보고서 수정 성공', { report_id: id, changes });
return { changes }; return { changes };
} catch (error) { } catch (error) {
if (error instanceof NotFoundError) { if (error instanceof NotFoundError) throw error;
throw error;
}
logger.error('작업 보고서 수정 실패', { report_id: id, error: error.message }); logger.error('작업 보고서 수정 실패', { report_id: id, error: error.message });
throw new DatabaseError('작업 보고서 수정 중 오류가 발생했습니다'); throw new DatabaseError('작업 보고서 수정 중 오류가 발생했습니다');
} }
@@ -195,12 +157,7 @@ const removeWorkReportService = async (id) => {
logger.info('작업 보고서 삭제 요청', { report_id: id }); logger.info('작업 보고서 삭제 요청', { report_id: id });
try { try {
const changes = await new Promise((resolve, reject) => { const changes = await workReportModel.remove(id);
workReportModel.remove(id, (err, affectedRows) => {
if (err) reject(err);
else resolve(affectedRows);
});
});
if (changes === 0) { if (changes === 0) {
logger.warn('작업 보고서를 찾을 수 없음', { report_id: id }); logger.warn('작업 보고서를 찾을 수 없음', { report_id: id });
@@ -208,13 +165,9 @@ const removeWorkReportService = async (id) => {
} }
logger.info('작업 보고서 삭제 성공', { report_id: id, changes }); logger.info('작업 보고서 삭제 성공', { report_id: id, changes });
return { changes }; return { changes };
} catch (error) { } catch (error) {
if (error instanceof NotFoundError) { if (error instanceof NotFoundError) throw error;
throw error;
}
logger.error('작업 보고서 삭제 실패', { report_id: id, error: error.message }); logger.error('작업 보고서 삭제 실패', { report_id: id, error: error.message });
throw new DatabaseError('작업 보고서 삭제 중 오류가 발생했습니다'); throw new DatabaseError('작업 보고서 삭제 중 오류가 발생했습니다');
} }
@@ -237,35 +190,18 @@ const getSummaryService = async (year, month) => {
logger.info('작업 보고서 월간 요약 조회 요청', { year, month, start, end }); logger.info('작업 보고서 월간 요약 조회 요청', { year, month, start, end });
try { try {
const rows = await new Promise((resolve, reject) => { const rows = await workReportModel.getByRange(start, end);
workReportModel.getByRange(start, end, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (!rows || rows.length === 0) { if (!rows || rows.length === 0) {
logger.warn('월간 요약 데이터 없음', { year, month }); logger.warn('월간 요약 데이터 없음', { year, month });
throw new NotFoundError('해당 기간의 작업 보고서가 없습니다'); throw new NotFoundError('해당 기간의 작업 보고서가 없습니다');
} }
logger.info('작업 보고서 월간 요약 조회 성공', { logger.info('작업 보고서 월간 요약 조회 성공', { year, month, count: rows.length });
year,
month,
count: rows.length
});
return rows; return rows;
} catch (error) { } catch (error) {
if (error instanceof NotFoundError) { if (error instanceof NotFoundError) throw error;
throw error; logger.error('작업 보고서 월간 요약 조회 실패', { year, month, error: error.message });
}
logger.error('작업 보고서 월간 요약 조회 실패', {
year,
month,
error: error.message
});
throw new DatabaseError('월간 요약 조회 중 오류가 발생했습니다'); throw new DatabaseError('월간 요약 조회 중 오류가 발생했습니다');
} }
}; };
@@ -274,7 +210,6 @@ const getSummaryService = async (year, month) => {
/** /**
* 작업 보고서의 부적합 원인 목록 조회 * 작업 보고서의 부적합 원인 목록 조회
* - error_type_id 또는 issue_report_id 중 하나로 연결
*/ */
const getReportDefectsService = async (reportId) => { const getReportDefectsService = async (reportId) => {
const db = await getDb(); const db = await getDb();
@@ -313,22 +248,16 @@ const getReportDefectsService = async (reportId) => {
/** /**
* 부적합 원인 저장 (전체 교체) * 부적합 원인 저장 (전체 교체)
* - error_type_id 또는 issue_report_id 중 하나 사용 가능
* - issue_report_id: 신고된 이슈와 연결
* - error_type_id: 기존 오류 유형 (레거시)
*/ */
const saveReportDefectsService = async (reportId, defects) => { const saveReportDefectsService = async (reportId, defects) => {
const db = await getDb(); const db = await getDb();
try { try {
await db.query('START TRANSACTION'); await db.query('START TRANSACTION');
// 기존 부적합 원인 삭제
await db.execute('DELETE FROM work_report_defects WHERE report_id = ?', [reportId]); await db.execute('DELETE FROM work_report_defects WHERE report_id = ?', [reportId]);
// 새 부적합 원인 추가
if (defects && defects.length > 0) { if (defects && defects.length > 0) {
for (const defect of defects) { for (const defect of defects) {
// issue_report_id > category_id/item_id > error_type_id 순으로 우선
await db.execute(` await db.execute(`
INSERT INTO work_report_defects (report_id, error_type_id, issue_report_id, category_id, item_id, defect_hours, note) INSERT INTO work_report_defects (report_id, error_type_id, issue_report_id, category_id, item_id, defect_hours, note)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
@@ -344,12 +273,10 @@ const saveReportDefectsService = async (reportId, defects) => {
} }
} }
// 총 부적합 시간 계산 및 daily_work_reports 업데이트
const totalErrorHours = defects const totalErrorHours = defects
? defects.reduce((sum, d) => sum + (parseFloat(d.defect_hours) || 0), 0) ? defects.reduce((sum, d) => sum + (parseFloat(d.defect_hours) || 0), 0)
: 0; : 0;
// 첫 번째 defect의 error_type_id를 대표값으로 (레거시 호환)
const firstErrorTypeId = defects && defects.length > 0 const firstErrorTypeId = defects && defects.length > 0
? (defects.find(d => d.error_type_id)?.error_type_id || null) ? (defects.find(d => d.error_type_id)?.error_type_id || null)
: null; : null;
@@ -380,7 +307,6 @@ const saveReportDefectsService = async (reportId, defects) => {
/** /**
* 부적합 원인 추가 (단일) * 부적합 원인 추가 (단일)
* - issue_report_id 또는 error_type_id 중 하나 사용
*/ */
const addReportDefectService = async (reportId, defectData) => { const addReportDefectService = async (reportId, defectData) => {
const db = await getDb(); const db = await getDb();
@@ -396,7 +322,6 @@ const addReportDefectService = async (reportId, defectData) => {
defectData.note || null defectData.note || null
]); ]);
// 총 부적합 시간 업데이트
await updateTotalErrorHours(reportId); await updateTotalErrorHours(reportId);
logger.info('부적합 원인 추가 성공', { reportId, defectId: result.insertId }); logger.info('부적합 원인 추가 성공', { reportId, defectId: result.insertId });
@@ -413,7 +338,6 @@ const addReportDefectService = async (reportId, defectData) => {
const removeReportDefectService = async (defectId) => { const removeReportDefectService = async (defectId) => {
const db = await getDb(); const db = await getDb();
try { try {
// report_id 먼저 조회
const [defect] = await db.execute('SELECT report_id FROM work_report_defects WHERE defect_id = ?', [defectId]); const [defect] = await db.execute('SELECT report_id FROM work_report_defects WHERE defect_id = ?', [defectId]);
if (defect.length === 0) { if (defect.length === 0) {
throw new NotFoundError('부적합 원인을 찾을 수 없습니다'); throw new NotFoundError('부적합 원인을 찾을 수 없습니다');
@@ -421,10 +345,7 @@ const removeReportDefectService = async (defectId) => {
const reportId = defect[0].report_id; const reportId = defect[0].report_id;
// 삭제
await db.execute('DELETE FROM work_report_defects WHERE defect_id = ?', [defectId]); await db.execute('DELETE FROM work_report_defects WHERE defect_id = ?', [defectId]);
// 총 부적합 시간 업데이트
await updateTotalErrorHours(reportId); await updateTotalErrorHours(reportId);
logger.info('부적합 원인 삭제 성공', { defectId, reportId }); logger.info('부적합 원인 삭제 성공', { defectId, reportId });
@@ -438,7 +359,6 @@ const removeReportDefectService = async (defectId) => {
/** /**
* 총 부적합 시간 업데이트 헬퍼 * 총 부적합 시간 업데이트 헬퍼
* - issue_report_id가 있는 경우도 고려
*/ */
const updateTotalErrorHours = async (reportId) => { const updateTotalErrorHours = async (reportId) => {
const db = await getDb(); const db = await getDb();
@@ -450,8 +370,6 @@ const updateTotalErrorHours = async (reportId) => {
const totalErrorHours = result[0].total || 0; const totalErrorHours = result[0].total || 0;
// 첫 번째 부적합 원인의 error_type_id를 대표값으로 사용 (레거시 호환)
// issue_report_id만 있는 경우 error_type_id는 null
const [firstDefect] = await db.execute(` const [firstDefect] = await db.execute(`
SELECT error_type_id FROM work_report_defects SELECT error_type_id FROM work_report_defects
WHERE report_id = ? AND error_type_id IS NOT NULL WHERE report_id = ? AND error_type_id IS NOT NULL
@@ -480,7 +398,6 @@ module.exports = {
updateWorkReportService, updateWorkReportService,
removeWorkReportService, removeWorkReportService,
getSummaryService, getSummaryService,
// 부적합 원인 관리
getReportDefectsService, getReportDefectsService,
saveReportDefectsService, saveReportDefectsService,
addReportDefectService, addReportDefectService,

View File

@@ -19,7 +19,6 @@ describe('WorkReportService', () => {
describe('createWorkReportService', () => { describe('createWorkReportService', () => {
it('단일 보고서를 성공적으로 생성해야 함', async () => { it('단일 보고서를 성공적으로 생성해야 함', async () => {
// Arrange
const reportData = { const reportData = {
report_date: '2025-12-11', report_date: '2025-12-11',
worker_id: 1, worker_id: 1,
@@ -29,46 +28,32 @@ describe('WorkReportService', () => {
work_content: '기능 개발' work_content: '기능 개발'
}; };
// workReportModel.create가 콜백 형태이므로 모킹 설정 workReportModel.create.mockResolvedValue(123);
workReportModel.create = jest.fn((data, callback) => {
callback(null, 123); // insertId = 123
});
// Act
const result = await workReportService.createWorkReportService(reportData); const result = await workReportService.createWorkReportService(reportData);
// Assert
expect(result).toEqual({ workReport_ids: [123] }); expect(result).toEqual({ workReport_ids: [123] });
expect(workReportModel.create).toHaveBeenCalledTimes(1); expect(workReportModel.create).toHaveBeenCalledTimes(1);
expect(workReportModel.create).toHaveBeenCalledWith( expect(workReportModel.create).toHaveBeenCalledWith(reportData);
reportData,
expect.any(Function)
);
}); });
it('다중 보고서를 성공적으로 생성해야 함', async () => { it('다중 보고서를 성공적으로 생성해야 함', async () => {
// Arrange
const reportsData = [ const reportsData = [
{ report_date: '2025-12-11', worker_id: 1, work_hours: 8 }, { report_date: '2025-12-11', worker_id: 1, work_hours: 8 },
{ report_date: '2025-12-11', worker_id: 2, work_hours: 7 } { report_date: '2025-12-11', worker_id: 2, work_hours: 7 }
]; ];
let callCount = 0; workReportModel.create
workReportModel.create = jest.fn((data, callback) => { .mockResolvedValueOnce(101)
callCount++; .mockResolvedValueOnce(102);
callback(null, 100 + callCount);
});
// Act
const result = await workReportService.createWorkReportService(reportsData); const result = await workReportService.createWorkReportService(reportsData);
// Assert
expect(result).toEqual({ workReport_ids: [101, 102] }); expect(result).toEqual({ workReport_ids: [101, 102] });
expect(workReportModel.create).toHaveBeenCalledTimes(2); expect(workReportModel.create).toHaveBeenCalledTimes(2);
}); });
it('빈 배열이면 ValidationError를 던져야 함', async () => { it('빈 배열이면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.createWorkReportService([])) await expect(workReportService.createWorkReportService([]))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
@@ -77,14 +62,10 @@ describe('WorkReportService', () => {
}); });
it('DB 오류 시 DatabaseError를 던져야 함', async () => { it('DB 오류 시 DatabaseError를 던져야 함', async () => {
// Arrange
const reportData = { report_date: '2025-12-11', worker_id: 1 }; const reportData = { report_date: '2025-12-11', worker_id: 1 };
workReportModel.create = jest.fn((data, callback) => { workReportModel.create.mockRejectedValue(new Error('DB connection failed'));
callback(new Error('DB connection failed'), null);
});
// Act & Assert
await expect(workReportService.createWorkReportService(reportData)) await expect(workReportService.createWorkReportService(reportData))
.rejects.toThrow(DatabaseError); .rejects.toThrow(DatabaseError);
}); });
@@ -92,24 +73,18 @@ describe('WorkReportService', () => {
describe('getWorkReportsByDateService', () => { describe('getWorkReportsByDateService', () => {
it('날짜로 보고서를 조회해야 함', async () => { it('날짜로 보고서를 조회해야 함', async () => {
// Arrange
const date = '2025-12-11'; const date = '2025-12-11';
const mockReports = mockWorkReports.filter(r => r.report_date === date); const mockReports = mockWorkReports.filter(r => r.report_date === date);
workReportModel.getAllByDate = jest.fn((date, callback) => { workReportModel.getAllByDate.mockResolvedValue(mockReports);
callback(null, mockReports);
});
// Act
const result = await workReportService.getWorkReportsByDateService(date); const result = await workReportService.getWorkReportsByDateService(date);
// Assert
expect(result).toEqual(mockReports); expect(result).toEqual(mockReports);
expect(workReportModel.getAllByDate).toHaveBeenCalledWith(date, expect.any(Function)); expect(workReportModel.getAllByDate).toHaveBeenCalledWith(date);
}); });
it('날짜가 없으면 ValidationError를 던져야 함', async () => { it('날짜가 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getWorkReportsByDateService(null)) await expect(workReportService.getWorkReportsByDateService(null))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
@@ -118,12 +93,8 @@ describe('WorkReportService', () => {
}); });
it('DB 오류 시 DatabaseError를 던져야 함', async () => { it('DB 오류 시 DatabaseError를 던져야 함', async () => {
// Arrange workReportModel.getAllByDate.mockRejectedValue(new Error('DB error'));
workReportModel.getAllByDate = jest.fn((date, callback) => {
callback(new Error('DB error'), null);
});
// Act & Assert
await expect(workReportService.getWorkReportsByDateService('2025-12-11')) await expect(workReportService.getWorkReportsByDateService('2025-12-11'))
.rejects.toThrow(DatabaseError); .rejects.toThrow(DatabaseError);
}); });
@@ -131,28 +102,19 @@ describe('WorkReportService', () => {
describe('getWorkReportByIdService', () => { describe('getWorkReportByIdService', () => {
it('ID로 보고서를 조회해야 함', async () => { it('ID로 보고서를 조회해야 함', async () => {
// Arrange
const mockReport = mockWorkReports[0]; const mockReport = mockWorkReports[0];
workReportModel.getById = jest.fn((id, callback) => { workReportModel.getById.mockResolvedValue(mockReport);
callback(null, mockReport);
});
// Act
const result = await workReportService.getWorkReportByIdService(1); const result = await workReportService.getWorkReportByIdService(1);
// Assert
expect(result).toEqual(mockReport); expect(result).toEqual(mockReport);
expect(workReportModel.getById).toHaveBeenCalledWith(1, expect.any(Function)); expect(workReportModel.getById).toHaveBeenCalledWith(1);
}); });
it('보고서가 없으면 NotFoundError를 던져야 함', async () => { it('보고서가 없으면 NotFoundError를 던져야 함', async () => {
// Arrange workReportModel.getById.mockResolvedValue(null);
workReportModel.getById = jest.fn((id, callback) => {
callback(null, null);
});
// Act & Assert
await expect(workReportService.getWorkReportByIdService(999)) await expect(workReportService.getWorkReportByIdService(999))
.rejects.toThrow(NotFoundError); .rejects.toThrow(NotFoundError);
@@ -161,7 +123,6 @@ describe('WorkReportService', () => {
}); });
it('ID가 없으면 ValidationError를 던져야 함', async () => { it('ID가 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getWorkReportByIdService(null)) await expect(workReportService.getWorkReportByIdService(null))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
@@ -169,38 +130,24 @@ describe('WorkReportService', () => {
describe('updateWorkReportService', () => { describe('updateWorkReportService', () => {
it('보고서를 성공적으로 수정해야 함', async () => { it('보고서를 성공적으로 수정해야 함', async () => {
// Arrange
const updateData = { work_hours: 9 }; const updateData = { work_hours: 9 };
workReportModel.update = jest.fn((id, data, callback) => { workReportModel.update.mockResolvedValue(1);
callback(null, 1); // affectedRows = 1
});
// Act
const result = await workReportService.updateWorkReportService(123, updateData); const result = await workReportService.updateWorkReportService(123, updateData);
// Assert
expect(result).toEqual({ changes: 1 }); expect(result).toEqual({ changes: 1 });
expect(workReportModel.update).toHaveBeenCalledWith( expect(workReportModel.update).toHaveBeenCalledWith(123, updateData);
123,
updateData,
expect.any(Function)
);
}); });
it('수정할 보고서가 없으면 NotFoundError를 던져야 함', async () => { it('수정할 보고서가 없으면 NotFoundError를 던져야 함', async () => {
// Arrange workReportModel.update.mockResolvedValue(0);
workReportModel.update = jest.fn((id, data, callback) => {
callback(null, 0); // affectedRows = 0
});
// Act & Assert
await expect(workReportService.updateWorkReportService(999, {})) await expect(workReportService.updateWorkReportService(999, {}))
.rejects.toThrow(NotFoundError); .rejects.toThrow(NotFoundError);
}); });
it('ID가 없으면 ValidationError를 던져야 함', async () => { it('ID가 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.updateWorkReportService(null, {})) await expect(workReportService.updateWorkReportService(null, {}))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
@@ -208,32 +155,22 @@ describe('WorkReportService', () => {
describe('removeWorkReportService', () => { describe('removeWorkReportService', () => {
it('보고서를 성공적으로 삭제해야 함', async () => { it('보고서를 성공적으로 삭제해야 함', async () => {
// Arrange workReportModel.remove.mockResolvedValue(1);
workReportModel.remove = jest.fn((id, callback) => {
callback(null, 1);
});
// Act
const result = await workReportService.removeWorkReportService(123); const result = await workReportService.removeWorkReportService(123);
// Assert
expect(result).toEqual({ changes: 1 }); expect(result).toEqual({ changes: 1 });
expect(workReportModel.remove).toHaveBeenCalledWith(123, expect.any(Function)); expect(workReportModel.remove).toHaveBeenCalledWith(123);
}); });
it('삭제할 보고서가 없으면 NotFoundError를 던져야 함', async () => { it('삭제할 보고서가 없으면 NotFoundError를 던져야 함', async () => {
// Arrange workReportModel.remove.mockResolvedValue(0);
workReportModel.remove = jest.fn((id, callback) => {
callback(null, 0);
});
// Act & Assert
await expect(workReportService.removeWorkReportService(999)) await expect(workReportService.removeWorkReportService(999))
.rejects.toThrow(NotFoundError); .rejects.toThrow(NotFoundError);
}); });
it('ID가 없으면 ValidationError를 던져야 함', async () => { it('ID가 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.removeWorkReportService(null)) await expect(workReportService.removeWorkReportService(null))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
@@ -241,34 +178,23 @@ describe('WorkReportService', () => {
describe('getWorkReportsInRangeService', () => { describe('getWorkReportsInRangeService', () => {
it('기간으로 보고서를 조회해야 함', async () => { it('기간으로 보고서를 조회해야 함', async () => {
// Arrange
const start = '2025-12-01'; const start = '2025-12-01';
const end = '2025-12-31'; const end = '2025-12-31';
workReportModel.getByRange = jest.fn((start, end, callback) => { workReportModel.getByRange.mockResolvedValue(mockWorkReports);
callback(null, mockWorkReports);
});
// Act
const result = await workReportService.getWorkReportsInRangeService(start, end); const result = await workReportService.getWorkReportsInRangeService(start, end);
// Assert
expect(result).toEqual(mockWorkReports); expect(result).toEqual(mockWorkReports);
expect(workReportModel.getByRange).toHaveBeenCalledWith( expect(workReportModel.getByRange).toHaveBeenCalledWith(start, end);
start,
end,
expect.any(Function)
);
}); });
it('시작일이 없으면 ValidationError를 던져야 함', async () => { it('시작일이 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getWorkReportsInRangeService(null, '2025-12-31')) await expect(workReportService.getWorkReportsInRangeService(null, '2025-12-31'))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
it('종료일이 없으면 ValidationError를 던져야 함', async () => { it('종료일이 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getWorkReportsInRangeService('2025-12-01', null)) await expect(workReportService.getWorkReportsInRangeService('2025-12-01', null))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
@@ -276,41 +202,30 @@ describe('WorkReportService', () => {
describe('getSummaryService', () => { describe('getSummaryService', () => {
it('월간 요약을 조회해야 함', async () => { it('월간 요약을 조회해야 함', async () => {
// Arrange
const year = '2025'; const year = '2025';
const month = '12'; const month = '12';
workReportModel.getByRange = jest.fn((start, end, callback) => { workReportModel.getByRange.mockResolvedValue(mockWorkReports);
callback(null, mockWorkReports);
});
// Act
const result = await workReportService.getSummaryService(year, month); const result = await workReportService.getSummaryService(year, month);
// Assert
expect(result).toEqual(mockWorkReports); expect(result).toEqual(mockWorkReports);
expect(workReportModel.getByRange).toHaveBeenCalled(); expect(workReportModel.getByRange).toHaveBeenCalled();
}); });
it('연도가 없으면 ValidationError를 던져야 함', async () => { it('연도가 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getSummaryService(null, '12')) await expect(workReportService.getSummaryService(null, '12'))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
it('월이 없으면 ValidationError를 던져야 함', async () => { it('월이 없으면 ValidationError를 던져야 함', async () => {
// Act & Assert
await expect(workReportService.getSummaryService('2025', null)) await expect(workReportService.getSummaryService('2025', null))
.rejects.toThrow(ValidationError); .rejects.toThrow(ValidationError);
}); });
it('데이터가 없으면 NotFoundError를 던져야 함', async () => { it('데이터가 없으면 NotFoundError를 던져야 함', async () => {
// Arrange workReportModel.getByRange.mockResolvedValue([]);
workReportModel.getByRange = jest.fn((start, end, callback) => {
callback(null, []);
});
// Act & Assert
await expect(workReportService.getSummaryService('2025', '12')) await expect(workReportService.getSummaryService('2025', '12'))
.rejects.toThrow(NotFoundError); .rejects.toThrow(NotFoundError);