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:
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
@@ -5,6 +5,7 @@
|
||||
|
||||
const vacationBalanceModel = require('../models/vacationBalanceModel');
|
||||
const vacationTypeModel = require('../models/vacationTypeModel');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const vacationBalanceController = {
|
||||
/**
|
||||
@@ -14,27 +15,11 @@ const vacationBalanceController = {
|
||||
async getByWorkerAndYear(req, res) {
|
||||
try {
|
||||
const { workerId, year } = req.params;
|
||||
|
||||
vacationBalanceModel.getByWorkerAndYear(workerId, year, (err, results) => {
|
||||
if (err) {
|
||||
console.error('휴가 잔액 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationBalanceModel.getByWorkerAndYear(workerId, year);
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getByWorkerAndYear 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 잔액 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '휴가 잔액을 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -45,27 +30,11 @@ const vacationBalanceController = {
|
||||
async getAllByYear(req, res) {
|
||||
try {
|
||||
const { year } = req.params;
|
||||
|
||||
vacationBalanceModel.getAllByYear(year, (err, results) => {
|
||||
if (err) {
|
||||
console.error('전체 휴가 잔액 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '전체 휴가 잔액을 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationBalanceModel.getAllByYear(year);
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getAllByYear 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('전체 휴가 잔액 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '전체 휴가 잔액을 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -75,17 +44,9 @@ const vacationBalanceController = {
|
||||
*/
|
||||
async createBalance(req, res) {
|
||||
try {
|
||||
const {
|
||||
worker_id,
|
||||
vacation_type_id,
|
||||
year,
|
||||
total_days,
|
||||
used_days,
|
||||
notes
|
||||
} = req.body;
|
||||
const { worker_id, vacation_type_id, year, total_days, used_days, notes } = req.body;
|
||||
const created_by = req.user.user_id;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!worker_id || !vacation_type_id || !year || total_days === undefined) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
@@ -94,54 +55,33 @@ const vacationBalanceController = {
|
||||
}
|
||||
|
||||
// 중복 체크
|
||||
vacationBalanceModel.getByWorkerTypeYear(worker_id, vacation_type_id, year, (err, existing) => {
|
||||
if (err) {
|
||||
console.error('중복 체크 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
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 existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, vacation_type_id, year);
|
||||
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
|
||||
};
|
||||
|
||||
const result = await vacationBalanceModel.create(balanceData);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '휴가 잔액이 생성되었습니다',
|
||||
data: { id: result.insertId }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('createBalance 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 잔액 생성 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -161,39 +101,18 @@ const vacationBalanceController = {
|
||||
updateData.updated_at = new Date();
|
||||
|
||||
if (Object.keys(updateData).length === 1) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '수정할 데이터가 없습니다'
|
||||
});
|
||||
return res.status(400).json({ success: false, message: '수정할 데이터가 없습니다' });
|
||||
}
|
||||
|
||||
vacationBalanceModel.update(id, updateData, (err, result) => {
|
||||
if (err) {
|
||||
console.error('휴가 잔액 수정 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 수정하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
const result = await vacationBalanceModel.update(id, updateData);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' });
|
||||
}
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '휴가 잔액이 수정되었습니다'
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '휴가 잔액이 수정되었습니다' });
|
||||
} catch (error) {
|
||||
console.error('updateBalance 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 잔액 수정 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -204,34 +123,16 @@ const vacationBalanceController = {
|
||||
async deleteBalance(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const result = await vacationBalanceModel.delete(id);
|
||||
|
||||
vacationBalanceModel.delete(id, (err, result) => {
|
||||
if (err) {
|
||||
console.error('휴가 잔액 삭제 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 삭제하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({ success: false, message: '휴가 잔액을 찾을 수 없습니다' });
|
||||
}
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '휴가 잔액을 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '휴가 잔액이 삭제되었습니다'
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '휴가 잔액이 삭제되었습니다' });
|
||||
} catch (error) {
|
||||
console.error('deleteBalance 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 잔액 삭제 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -251,74 +152,44 @@ const vacationBalanceController = {
|
||||
});
|
||||
}
|
||||
|
||||
// 연차 일수 계산
|
||||
const annualDays = vacationBalanceModel.calculateAnnualLeaveDays(hire_date, year);
|
||||
|
||||
// ANNUAL 휴가 유형 ID 조회
|
||||
vacationTypeModel.getByCode('ANNUAL', (err, types) => {
|
||||
if (err || !types || types.length === 0) {
|
||||
console.error('ANNUAL 휴가 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'ANNUAL 휴가 유형을 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
const types = await vacationTypeModel.getByCode('ANNUAL');
|
||||
if (!types || types.length === 0) {
|
||||
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) => {
|
||||
if (err) {
|
||||
console.error('중복 체크 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
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 existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, annualTypeId, year);
|
||||
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
|
||||
};
|
||||
|
||||
const result = await vacationBalanceModel.create(balanceData);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: `${annualDays}일의 연차가 자동으로 생성되었습니다`,
|
||||
data: { id: result.insertId, calculated_days: annualDays }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('autoCalculateAndCreate 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('연차 자동 계산 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -332,10 +203,7 @@ const vacationBalanceController = {
|
||||
const created_by = req.user.user_id;
|
||||
|
||||
if (!balances || !Array.isArray(balances) || balances.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '저장할 데이터가 없습니다'
|
||||
});
|
||||
return res.status(400).json({ success: false, message: '저장할 데이터가 없습니다' });
|
||||
}
|
||||
|
||||
const { getDb } = require('../dbPool');
|
||||
@@ -353,7 +221,6 @@ const vacationBalanceController = {
|
||||
}
|
||||
|
||||
try {
|
||||
// Upsert 쿼리
|
||||
const query = `
|
||||
INSERT INTO vacation_balance_details
|
||||
(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]);
|
||||
successCount++;
|
||||
} catch (err) {
|
||||
console.error('휴가 잔액 저장 오류:', err);
|
||||
logger.error('휴가 잔액 저장 오류:', err);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
@@ -378,11 +245,8 @@ const vacationBalanceController = {
|
||||
data: { successCount, errorCount }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('bulkUpsert 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('bulkUpsert 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -393,27 +257,11 @@ const vacationBalanceController = {
|
||||
async getAvailableDays(req, res) {
|
||||
try {
|
||||
const { workerId, year } = req.params;
|
||||
|
||||
vacationBalanceModel.getAvailableVacationDays(workerId, year, (err, results) => {
|
||||
if (err) {
|
||||
console.error('사용 가능 휴가 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '사용 가능 휴가를 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationBalanceModel.getAvailableVacationDays(workerId, year);
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getAvailableDays 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('사용 가능 휴가 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '사용 가능 휴가를 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
const vacationTypeModel = require('../models/vacationTypeModel');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const vacationTypeController = {
|
||||
/**
|
||||
@@ -12,26 +13,11 @@ const vacationTypeController = {
|
||||
*/
|
||||
async getAllTypes(req, res) {
|
||||
try {
|
||||
vacationTypeModel.getAll((err, results) => {
|
||||
if (err) {
|
||||
console.error('휴가 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 유형을 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationTypeModel.getAll();
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getAllTypes 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 유형 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '휴가 유형을 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -41,26 +27,11 @@ const vacationTypeController = {
|
||||
*/
|
||||
async getSystemTypes(req, res) {
|
||||
try {
|
||||
vacationTypeModel.getSystemTypes((err, results) => {
|
||||
if (err) {
|
||||
console.error('시스템 휴가 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '시스템 휴가 유형을 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationTypeModel.getSystemTypes();
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getSystemTypes 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('시스템 휴가 유형 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '시스템 휴가 유형을 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -70,26 +41,11 @@ const vacationTypeController = {
|
||||
*/
|
||||
async getSpecialTypes(req, res) {
|
||||
try {
|
||||
vacationTypeModel.getSpecialTypes((err, results) => {
|
||||
if (err) {
|
||||
console.error('특별 휴가 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '특별 휴가 유형을 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
});
|
||||
const results = await vacationTypeModel.getSpecialTypes();
|
||||
res.json({ success: true, data: results });
|
||||
} catch (error) {
|
||||
console.error('getSpecialTypes 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('특별 휴가 유형 조회 오류:', error);
|
||||
res.status(500).json({ success: false, message: '특별 휴가 유형을 조회하는 중 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -99,15 +55,8 @@ const vacationTypeController = {
|
||||
*/
|
||||
async createType(req, res) {
|
||||
try {
|
||||
const {
|
||||
type_code,
|
||||
type_name,
|
||||
deduct_days,
|
||||
priority,
|
||||
description
|
||||
} = req.body;
|
||||
const { type_code, type_name, deduct_days, priority, description } = req.body;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!type_code || !type_name || !deduct_days) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
@@ -116,56 +65,31 @@ const vacationTypeController = {
|
||||
}
|
||||
|
||||
// type_code 중복 체크
|
||||
vacationTypeModel.getByCode(type_code, (err, existingTypes) => {
|
||||
if (err) {
|
||||
console.error('type_code 중복 체크 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'type_code 중복 체크 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
const existingTypes = await vacationTypeModel.getByCode(type_code);
|
||||
if (existingTypes && existingTypes.length > 0) {
|
||||
return res.status(400).json({ success: false, message: '이미 존재하는 type_code입니다' });
|
||||
}
|
||||
|
||||
if (existingTypes && existingTypes.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '이미 존재하는 type_code입니다'
|
||||
});
|
||||
}
|
||||
const typeData = {
|
||||
type_code,
|
||||
type_name,
|
||||
deduct_days,
|
||||
priority: priority || 50,
|
||||
description: description || null,
|
||||
is_special: true,
|
||||
is_system: false,
|
||||
is_active: true
|
||||
};
|
||||
|
||||
// 특별 휴가 유형으로 생성
|
||||
const typeData = {
|
||||
type_code,
|
||||
type_name,
|
||||
deduct_days,
|
||||
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 }
|
||||
});
|
||||
});
|
||||
const result = await vacationTypeModel.create(typeData);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '특별 휴가 유형이 생성되었습니다',
|
||||
data: { id: result.insertId }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('createType 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 유형 생성 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -176,78 +100,37 @@ const vacationTypeController = {
|
||||
async updateType(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
type_name,
|
||||
deduct_days,
|
||||
priority,
|
||||
description,
|
||||
is_active
|
||||
} = req.body;
|
||||
const { type_name, deduct_days, priority, description, is_active } = req.body;
|
||||
|
||||
// 먼저 해당 유형 조회
|
||||
vacationTypeModel.getById(id, (err, types) => {
|
||||
if (err) {
|
||||
console.error('휴가 유형 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 유형을 조회하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
const types = await vacationTypeModel.getById(id);
|
||||
if (!types || types.length === 0) {
|
||||
return res.status(404).json({ success: false, message: '휴가 유형을 찾을 수 없습니다' });
|
||||
}
|
||||
|
||||
if (!types || types.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '휴가 유형을 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
const type = types[0];
|
||||
const updateData = {};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 시스템 기본 휴가의 경우 제한적으로만 수정 가능
|
||||
const updateData = {};
|
||||
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) {
|
||||
return res.status(400).json({ success: false, message: '수정할 데이터가 없습니다' });
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
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: '휴가 유형이 수정되었습니다'
|
||||
});
|
||||
});
|
||||
});
|
||||
updateData.updated_at = new Date();
|
||||
await vacationTypeModel.update(id, updateData);
|
||||
res.json({ success: true, message: '휴가 유형이 수정되었습니다' });
|
||||
} catch (error) {
|
||||
console.error('updateType 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 유형 수정 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -258,34 +141,19 @@ const vacationTypeController = {
|
||||
async deleteType(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const result = await vacationTypeModel.delete(id);
|
||||
|
||||
vacationTypeModel.delete(id, (err, result) => {
|
||||
if (err) {
|
||||
console.error('휴가 유형 삭제 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '휴가 유형을 삭제하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '삭제할 수 없습니다. 시스템 기본 휴가이거나 존재하지 않는 휴가 유형입니다'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '휴가 유형이 삭제되었습니다'
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '삭제할 수 없습니다. 시스템 기본 휴가이거나 존재하지 않는 휴가 유형입니다'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ success: true, message: '휴가 유형이 삭제되었습니다' });
|
||||
} catch (error) {
|
||||
console.error('deleteType 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('휴가 유형 삭제 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -297,35 +165,22 @@ const vacationTypeController = {
|
||||
try {
|
||||
const { priorities } = req.body;
|
||||
|
||||
// priorities = [{ id: 1, priority: 10 }, { id: 2, priority: 20 }, ...]
|
||||
if (!priorities || !Array.isArray(priorities)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'priorities 배열이 필요합니다'
|
||||
});
|
||||
return res.status(400).json({ success: false, message: 'priorities 배열이 필요합니다' });
|
||||
}
|
||||
|
||||
vacationTypeModel.updatePriorities(priorities, (err, result) => {
|
||||
if (err) {
|
||||
console.error('우선순위 업데이트 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '우선순위를 업데이트하는 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
for (const { id, priority } of priorities) {
|
||||
await vacationTypeModel.updatePriority(id, priority);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '우선순위가 업데이트되었습니다',
|
||||
data: { updated: result.affectedRows }
|
||||
});
|
||||
res.json({
|
||||
success: true,
|
||||
message: '우선순위가 업데이트되었습니다',
|
||||
data: { updated: priorities.length }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('updatePriorities 오류:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다'
|
||||
});
|
||||
logger.error('우선순위 업데이트 오류:', error);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,555 +1,284 @@
|
||||
const visitRequestModel = require('../models/visitRequestModel');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// ==================== 출입 신청 관리 ====================
|
||||
|
||||
/**
|
||||
* 출입 신청 생성
|
||||
*/
|
||||
exports.createVisitRequest = (req, res) => {
|
||||
const requester_id = req.user.user_id;
|
||||
const requestData = {
|
||||
requester_id,
|
||||
...req.body
|
||||
};
|
||||
exports.createVisitRequest = async (req, res) => {
|
||||
try {
|
||||
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'];
|
||||
for (const field of requiredFields) {
|
||||
if (!requestData[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 requiredFields = ['visitor_company', 'category_id', 'workplace_id', 'visit_date', 'visit_time', 'purpose_id'];
|
||||
for (const field of requiredFields) {
|
||||
if (!requestData[field]) {
|
||||
return res.status(400).json({ success: false, message: `${field}는 필수 입력 항목입니다.` });
|
||||
}
|
||||
}
|
||||
|
||||
const requestId = await visitRequestModel.createVisitRequest(requestData);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '출입 신청이 성공적으로 생성되었습니다.',
|
||||
data: { request_id: requestId }
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('출입 신청 생성 오류:', err);
|
||||
res.status(500).json({ success: false, message: '출입 신청 생성 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 목록 조회
|
||||
*/
|
||||
exports.getAllVisitRequests = (req, res) => {
|
||||
const filters = {
|
||||
status: req.query.status,
|
||||
visit_date: req.query.visit_date,
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
requester_id: req.query.requester_id,
|
||||
category_id: req.query.category_id
|
||||
};
|
||||
exports.getAllVisitRequests = async (req, res) => {
|
||||
try {
|
||||
const filters = {
|
||||
status: req.query.status,
|
||||
visit_date: req.query.visit_date,
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
requester_id: req.query.requester_id,
|
||||
category_id: req.query.category_id
|
||||
};
|
||||
|
||||
visitRequestModel.getAllVisitRequests(filters, (err, requests) => {
|
||||
if (err) {
|
||||
console.error('출입 신청 목록 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '출입 신청 목록 조회 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: requests
|
||||
});
|
||||
});
|
||||
const requests = await visitRequestModel.getAllVisitRequests(filters);
|
||||
res.json({ success: true, data: requests });
|
||||
} catch (err) {
|
||||
logger.error('출입 신청 목록 조회 오류:', err);
|
||||
res.status(500).json({ success: false, message: '출입 신청 목록 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 상세 조회
|
||||
*/
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
exports.getVisitRequestById = async (req, res) => {
|
||||
try {
|
||||
const request = await visitRequestModel.getVisitRequestById(req.params.id);
|
||||
if (!request) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '출입 신청을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: request
|
||||
});
|
||||
});
|
||||
res.json({ success: true, data: request });
|
||||
} catch (err) {
|
||||
logger.error('출입 신청 조회 오류:', err);
|
||||
res.status(500).json({ success: false, message: '출입 신청 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 수정
|
||||
*/
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
exports.updateVisitRequest = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.updateVisitRequest(req.params.id, req.body);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '출입 신청을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '출입 신청이 수정되었습니다.'
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '출입 신청이 수정되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('출입 신청 수정 오류:', err);
|
||||
res.status(500).json({ success: false, message: '출입 신청 수정 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 삭제
|
||||
*/
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
exports.deleteVisitRequest = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.deleteVisitRequest(req.params.id);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '출입 신청을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '출입 신청이 삭제되었습니다.'
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '출입 신청이 삭제되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('출입 신청 삭제 오류:', err);
|
||||
res.status(500).json({ success: false, message: '출입 신청 삭제 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 승인
|
||||
*/
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
exports.approveVisitRequest = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.approveVisitRequest(req.params.id, req.user.user_id);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '출입 신청을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '출입 신청이 승인되었습니다.'
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '출입 신청이 승인되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('출입 신청 승인 오류:', err);
|
||||
res.status(500).json({ success: false, message: '출입 신청 승인 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 반려
|
||||
*/
|
||||
exports.rejectVisitRequest = (req, res) => {
|
||||
const requestId = req.params.id;
|
||||
const approvedBy = req.user.user_id;
|
||||
const rejectionReason = req.body.rejection_reason || '사유 없음';
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
exports.rejectVisitRequest = async (req, res) => {
|
||||
try {
|
||||
const rejectionData = {
|
||||
approved_by: req.user.user_id,
|
||||
rejection_reason: req.body.rejection_reason || '사유 없음'
|
||||
};
|
||||
const result = await visitRequestModel.rejectVisitRequest(req.params.id, rejectionData);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '출입 신청을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '출입 신청이 반려되었습니다.'
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '출입 신청이 반려되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('출입 신청 반려 오류:', err);
|
||||
res.status(500).json({ success: false, message: '출입 신청 반려 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 방문 목적 관리 ====================
|
||||
|
||||
/**
|
||||
* 모든 방문 목적 조회
|
||||
*/
|
||||
exports.getAllVisitPurposes = (req, res) => {
|
||||
visitRequestModel.getAllVisitPurposes((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.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은 필수 입력 항목입니다.'
|
||||
});
|
||||
exports.getAllVisitPurposes = async (req, res) => {
|
||||
try {
|
||||
const purposes = await visitRequestModel.getAllVisitPurposes();
|
||||
res.json({ success: true, data: purposes });
|
||||
} catch (err) {
|
||||
logger.error('방문 목적 조회 오류:', err);
|
||||
res.status(500).json({ success: false, message: '방문 목적 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
visitRequestModel.createVisitPurpose(purposeData, (err, purposeId) => {
|
||||
if (err) {
|
||||
console.error('방문 목적 추가 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '방문 목적 추가 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
exports.getActiveVisitPurposes = async (req, res) => {
|
||||
try {
|
||||
const purposes = await visitRequestModel.getActiveVisitPurposes();
|
||||
res.json({ success: true, data: purposes });
|
||||
} catch (err) {
|
||||
logger.error('활성 방문 목적 조회 오류:', err);
|
||||
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({
|
||||
success: true,
|
||||
message: '방문 목적이 추가되었습니다.',
|
||||
data: { purpose_id: purposeId }
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('방문 목적 추가 오류:', err);
|
||||
res.status(500).json({ success: false, message: '방문 목적 추가 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 방문 목적 수정
|
||||
*/
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
exports.updateVisitPurpose = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.updateVisitPurpose(req.params.id, req.body);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '방문 목적을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ success: false, message: '방문 목적을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '방문 목적이 수정되었습니다.'
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '방문 목적이 수정되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('방문 목적 수정 오류:', err);
|
||||
res.status(500).json({ success: false, message: '방문 목적 수정 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 방문 목적 삭제
|
||||
*/
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
exports.deleteVisitPurpose = async (req, res) => {
|
||||
try {
|
||||
const result = await visitRequestModel.deleteVisitPurpose(req.params.id);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '방문 목적을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ success: false, message: '방문 목적을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '방문 목적이 삭제되었습니다.'
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '방문 목적이 삭제되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('방문 목적 삭제 오류:', err);
|
||||
res.status(500).json({ success: false, message: '방문 목적 삭제 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 안전교육 기록 관리 ====================
|
||||
|
||||
/**
|
||||
* 안전교육 기록 생성
|
||||
*/
|
||||
exports.createTrainingRecord = (req, res) => {
|
||||
const trainerId = req.user.user_id;
|
||||
const trainingData = {
|
||||
trainer_id: trainerId,
|
||||
...req.body
|
||||
};
|
||||
exports.createTrainingRecord = async (req, res) => {
|
||||
try {
|
||||
const trainingData = { trainer_id: req.user.user_id, ...req.body };
|
||||
|
||||
// 필수 필드 검증
|
||||
const requiredFields = ['request_id', 'training_date', 'training_start_time'];
|
||||
for (const field of requiredFields) {
|
||||
if (!trainingData[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} 상태 변경 성공`);
|
||||
const requiredFields = ['request_id', 'training_date', 'training_start_time'];
|
||||
for (const field of requiredFields) {
|
||||
if (!trainingData[field]) {
|
||||
return res.status(400).json({ success: false, message: `${field}는 필수 입력 항목입니다.` });
|
||||
}
|
||||
|
||||
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,
|
||||
data: record || null
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 안전교육 기록 수정
|
||||
*/
|
||||
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: '서명 데이터가 필요합니다.'
|
||||
message: '안전교육 기록이 생성되었습니다.',
|
||||
data: { training_id: trainingId }
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('안전교육 기록 생성 오류:', err);
|
||||
res.status(500).json({ success: false, message: '안전교육 기록 생성 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
visitRequestModel.completeTraining(trainingId, signatureData, (err, result) => {
|
||||
if (err) {
|
||||
console.error('안전교육 완료 처리 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '안전교육 완료 처리 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
exports.getTrainingRecordByRequestId = async (req, res) => {
|
||||
try {
|
||||
const record = await visitRequestModel.getTrainingRecordByRequestId(req.params.requestId);
|
||||
res.json({ success: true, data: record || null });
|
||||
} catch (err) {
|
||||
logger.error('안전교육 기록 조회 오류:', err);
|
||||
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) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '안전교육 기록을 찾을 수 없습니다.'
|
||||
});
|
||||
return res.status(404).json({ 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'로 변경
|
||||
visitRequestModel.getTrainingRecordByRequestId(trainingId, (err, record) => {
|
||||
if (err || !record) {
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '안전교육이 완료되었습니다.'
|
||||
});
|
||||
const result = await visitRequestModel.completeTraining(trainingId, signatureData);
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({ success: false, message: '안전교육 기록을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 교육 완료 후 출입 신청 상태 변경
|
||||
try {
|
||||
const record = await visitRequestModel.getTrainingRecordByRequestId(trainingId);
|
||||
if (record) {
|
||||
await visitRequestModel.updateVisitRequestStatus(record.request_id, 'training_completed');
|
||||
}
|
||||
|
||||
visitRequestModel.updateVisitRequestStatus(record.request_id, 'training_completed', (err) => {
|
||||
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
|
||||
});
|
||||
} catch (statusErr) {
|
||||
logger.error('출입 신청 상태 업데이트 오류:', statusErr);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: records
|
||||
});
|
||||
});
|
||||
res.json({ success: true, message: '안전교육이 완료되었습니다.' });
|
||||
} catch (err) {
|
||||
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: '안전교육 기록 목록 조회 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,198 +4,149 @@
|
||||
|
||||
const workIssueModel = require('../models/workIssueModel');
|
||||
const imageUploadService = require('../services/imageUploadService');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// ==================== 신고 카테고리 관리 ====================
|
||||
|
||||
/**
|
||||
* 모든 카테고리 조회
|
||||
*/
|
||||
exports.getAllCategories = (req, res) => {
|
||||
workIssueModel.getAllCategories((err, categories) => {
|
||||
if (err) {
|
||||
console.error('카테고리 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '카테고리 조회 실패' });
|
||||
}
|
||||
exports.getAllCategories = async (req, res) => {
|
||||
try {
|
||||
const categories = await workIssueModel.getAllCategories();
|
||||
res.json({ success: true, data: categories });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('카테고리 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '카테고리 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 타입별 카테고리 조회
|
||||
*/
|
||||
exports.getCategoriesByType = (req, res) => {
|
||||
const { type } = req.params;
|
||||
exports.getCategoriesByType = async (req, res) => {
|
||||
try {
|
||||
const { type } = req.params;
|
||||
|
||||
if (!['nonconformity', 'safety', 'facility'].includes(type)) {
|
||||
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: '카테고리 조회 실패' });
|
||||
if (!['nonconformity', 'safety', 'facility'].includes(type)) {
|
||||
return res.status(400).json({ success: false, error: '유효하지 않은 카테고리 타입입니다.' });
|
||||
}
|
||||
|
||||
const categories = await workIssueModel.getCategoriesByType(type);
|
||||
res.json({ success: true, data: categories });
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리 생성
|
||||
*/
|
||||
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: '카테고리 타입과 이름은 필수입니다.' });
|
||||
} catch (err) {
|
||||
logger.error('카테고리 조회 실패:', err);
|
||||
res.status(500).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.updateCategory = (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { category_name, description, display_order, is_active } = req.body;
|
||||
exports.createCategory = async (req, res) => {
|
||||
try {
|
||||
const { category_type, category_name, description, display_order } = req.body;
|
||||
|
||||
workIssueModel.updateCategory(
|
||||
id,
|
||||
{ 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: '카테고리가 수정되었습니다.' });
|
||||
if (!category_type || !category_name) {
|
||||
return res.status(400).json({ success: false, error: '카테고리 타입과 이름은 필수입니다.' });
|
||||
}
|
||||
);
|
||||
|
||||
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.deleteCategory = (req, res) => {
|
||||
const { id } = req.params;
|
||||
exports.updateCategory = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { category_name, description, display_order, is_active } = req.body;
|
||||
|
||||
workIssueModel.deleteCategory(id, (err, result) => {
|
||||
if (err) {
|
||||
console.error('카테고리 삭제 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '카테고리 삭제 실패' });
|
||||
}
|
||||
await workIssueModel.updateCategory(id, { category_name, description, display_order, is_active });
|
||||
res.json({ success: true, message: '카테고리가 수정되었습니다.' });
|
||||
} catch (err) {
|
||||
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: '카테고리가 삭제되었습니다.' });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('카테고리 삭제 실패:', err);
|
||||
res.status(500).json({ success: false, error: '카테고리 삭제 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 사전 정의 항목 관리 ====================
|
||||
|
||||
/**
|
||||
* 카테고리별 항목 조회
|
||||
*/
|
||||
exports.getItemsByCategory = (req, res) => {
|
||||
const { categoryId } = req.params;
|
||||
|
||||
workIssueModel.getItemsByCategory(categoryId, (err, items) => {
|
||||
if (err) {
|
||||
console.error('항목 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '항목 조회 실패' });
|
||||
}
|
||||
exports.getItemsByCategory = async (req, res) => {
|
||||
try {
|
||||
const { categoryId } = req.params;
|
||||
const items = await workIssueModel.getItemsByCategory(categoryId);
|
||||
res.json({ success: true, data: items });
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 모든 항목 조회
|
||||
*/
|
||||
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와 항목명은 필수입니다.' });
|
||||
} catch (err) {
|
||||
logger.error('항목 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '항목 조회 실패' });
|
||||
}
|
||||
|
||||
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.updateItem = (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { item_name, description, severity, display_order, is_active } = req.body;
|
||||
|
||||
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.getAllItems = async (req, res) => {
|
||||
try {
|
||||
const items = await workIssueModel.getAllItems();
|
||||
res.json({ success: true, data: items });
|
||||
} catch (err) {
|
||||
logger.error('항목 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '항목 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 항목 삭제
|
||||
*/
|
||||
exports.deleteItem = (req, res) => {
|
||||
const { id } = req.params;
|
||||
exports.createItem = async (req, res) => {
|
||||
try {
|
||||
const { category_id, item_name, description, severity, display_order } = req.body;
|
||||
|
||||
workIssueModel.deleteItem(id, (err, result) => {
|
||||
if (err) {
|
||||
console.error('항목 삭제 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '항목 삭제 실패' });
|
||||
if (!category_id || !item_name) {
|
||||
return res.status(400).json({ success: false, error: '카테고리 ID와 항목명은 필수입니다.' });
|
||||
}
|
||||
|
||||
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: '항목이 삭제되었습니다.' });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('항목 삭제 실패:', err);
|
||||
res.status(500).json({ success: false, error: '항목 삭제 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 문제 신고 관리 ====================
|
||||
|
||||
/**
|
||||
* 신고 생성
|
||||
*/
|
||||
exports.createReport = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
@@ -206,7 +157,7 @@ exports.createReport = async (req, res) => {
|
||||
visit_request_id,
|
||||
issue_category_id,
|
||||
issue_item_id,
|
||||
custom_item_name, // 직접 입력한 항목명
|
||||
custom_item_name,
|
||||
additional_description,
|
||||
photos = []
|
||||
} = req.body;
|
||||
@@ -217,42 +168,25 @@ exports.createReport = async (req, res) => {
|
||||
return res.status(400).json({ success: false, error: '신고 카테고리는 필수입니다.' });
|
||||
}
|
||||
|
||||
// 위치 정보 검증 (지도 선택 또는 기타 위치)
|
||||
if (!factory_category_id && !custom_location) {
|
||||
return res.status(400).json({ success: false, error: '위치 정보는 필수입니다.' });
|
||||
}
|
||||
|
||||
// 항목 검증 (기존 항목 또는 직접 입력)
|
||||
if (!issue_item_id && !custom_item_name) {
|
||||
return res.status(400).json({ success: false, error: '신고 항목은 필수입니다.' });
|
||||
}
|
||||
|
||||
// 직접 입력한 항목이 있으면 DB에 저장
|
||||
let finalItemId = issue_item_id;
|
||||
if (custom_item_name && !issue_item_id) {
|
||||
try {
|
||||
finalItemId = await new Promise((resolve, reject) => {
|
||||
workIssueModel.createItem(
|
||||
{
|
||||
category_id: issue_category_id,
|
||||
item_name: custom_item_name,
|
||||
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: '항목 저장 실패' });
|
||||
}
|
||||
finalItemId = await workIssueModel.createItem({
|
||||
category_id: issue_category_id,
|
||||
item_name: custom_item_name,
|
||||
description: '사용자 직접 입력',
|
||||
severity: 'medium',
|
||||
display_order: 999
|
||||
});
|
||||
}
|
||||
|
||||
// 사진 저장 (최대 5장)
|
||||
const photoPaths = {
|
||||
photo_path1: null,
|
||||
photo_path2: null,
|
||||
@@ -283,73 +217,56 @@ exports.createReport = async (req, res) => {
|
||||
...photoPaths
|
||||
};
|
||||
|
||||
workIssueModel.createReport(reportData, (err, reportId) => {
|
||||
if (err) {
|
||||
console.error('신고 생성 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '신고 생성 실패' });
|
||||
}
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '문제 신고가 등록되었습니다.',
|
||||
data: { report_id: reportId }
|
||||
});
|
||||
const reportId = await workIssueModel.createReport(reportData);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '문제 신고가 등록되었습니다.',
|
||||
data: { report_id: reportId }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('신고 생성 에러:', error);
|
||||
} catch (err) {
|
||||
logger.error('신고 생성 실패:', err);
|
||||
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 목록 조회
|
||||
*/
|
||||
exports.getAllReports = (req, res) => {
|
||||
const filters = {
|
||||
status: req.query.status,
|
||||
category_type: req.query.category_type,
|
||||
issue_category_id: req.query.issue_category_id,
|
||||
factory_category_id: req.query.factory_category_id,
|
||||
workplace_id: req.query.workplace_id,
|
||||
assigned_user_id: req.query.assigned_user_id,
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
search: req.query.search,
|
||||
limit: req.query.limit,
|
||||
offset: req.query.offset
|
||||
};
|
||||
exports.getAllReports = async (req, res) => {
|
||||
try {
|
||||
const filters = {
|
||||
status: req.query.status,
|
||||
category_type: req.query.category_type,
|
||||
issue_category_id: req.query.issue_category_id,
|
||||
factory_category_id: req.query.factory_category_id,
|
||||
workplace_id: req.query.workplace_id,
|
||||
assigned_user_id: req.query.assigned_user_id,
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
search: req.query.search,
|
||||
limit: req.query.limit,
|
||||
offset: req.query.offset
|
||||
};
|
||||
|
||||
// 일반 사용자는 자신의 신고만 조회 (관리자 제외)
|
||||
const userLevel = req.user.access_level;
|
||||
if (!['admin', 'system', 'support_team'].includes(userLevel)) {
|
||||
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 userLevel = req.user.access_level;
|
||||
if (!['admin', 'system', 'support_team'].includes(userLevel)) {
|
||||
filters.reporter_id = req.user.user_id;
|
||||
}
|
||||
|
||||
const reports = await workIssueModel.getAllReports(filters);
|
||||
res.json({ success: true, data: reports });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('신고 목록 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '신고 목록 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 상세 조회
|
||||
*/
|
||||
exports.getReportById = (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
workIssueModel.getReportById(id, (err, report) => {
|
||||
if (err) {
|
||||
console.error('신고 상세 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '신고 상세 조회 실패' });
|
||||
}
|
||||
exports.getReportById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const report = await workIssueModel.getReportById(id);
|
||||
|
||||
if (!report) {
|
||||
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 권한 확인: 본인, 담당자, 또는 관리자
|
||||
const userLevel = req.user.access_level;
|
||||
const isOwner = report.reporter_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 });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('신고 상세 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '신고 상세 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 수정
|
||||
*/
|
||||
exports.updateReport = async (req, res) => {
|
||||
try {
|
||||
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) {
|
||||
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 권한 확인
|
||||
const userLevel = req.user.access_level;
|
||||
const isOwner = report.reporter_id === req.user.user_id;
|
||||
const isManager = ['admin', 'system'].includes(userLevel);
|
||||
|
||||
if (!isOwner && !isManager) {
|
||||
return res.status(403).json({ success: false, error: '수정 권한이 없습니다.' });
|
||||
}
|
||||
|
||||
// 상태 확인: reported 상태에서만 수정 가능 (관리자 제외)
|
||||
if (!isManager && report.status !== 'reported') {
|
||||
return res.status(400).json({ success: false, error: '이미 접수된 신고는 수정할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const {
|
||||
factory_category_id,
|
||||
workplace_id,
|
||||
custom_location,
|
||||
issue_category_id,
|
||||
issue_item_id,
|
||||
additional_description,
|
||||
photos = []
|
||||
} = req.body;
|
||||
|
||||
// 사진 업데이트 처리
|
||||
const photoPaths = {};
|
||||
for (let i = 0; i < Math.min(photos.length, 5); i++) {
|
||||
if (photos[i]) {
|
||||
// 기존 사진 삭제
|
||||
const oldPath = report[`photo_path${i + 1}`];
|
||||
if (oldPath) {
|
||||
await imageUploadService.deleteFile(oldPath);
|
||||
}
|
||||
// 새 사진 저장
|
||||
const savedPath = await imageUploadService.saveBase64Image(photos[i], 'issue');
|
||||
if (savedPath) {
|
||||
photoPaths[`photo_path${i + 1}`] = savedPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
factory_category_id,
|
||||
workplace_id,
|
||||
custom_location,
|
||||
issue_category_id,
|
||||
issue_item_id,
|
||||
additional_description,
|
||||
...photoPaths
|
||||
};
|
||||
|
||||
workIssueModel.updateReport(id, updateData, req.user.user_id, (updateErr, result) => {
|
||||
if (updateErr) {
|
||||
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: '신고 조회 실패' });
|
||||
}
|
||||
|
||||
const report = await workIssueModel.getReportById(id);
|
||||
if (!report) {
|
||||
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
const userLevel = req.user.access_level;
|
||||
const isOwner = report.reporter_id === req.user.user_id;
|
||||
const isManager = ['admin', 'system'].includes(userLevel);
|
||||
|
||||
if (!isOwner && !isManager) {
|
||||
return res.status(403).json({ success: false, error: '수정 권한이 없습니다.' });
|
||||
}
|
||||
|
||||
if (!isManager && report.status !== 'reported') {
|
||||
return res.status(400).json({ success: false, error: '이미 접수된 신고는 수정할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const {
|
||||
factory_category_id,
|
||||
workplace_id,
|
||||
custom_location,
|
||||
issue_category_id,
|
||||
issue_item_id,
|
||||
additional_description,
|
||||
photos = []
|
||||
} = req.body;
|
||||
|
||||
const photoPaths = {};
|
||||
for (let i = 0; i < Math.min(photos.length, 5); i++) {
|
||||
if (photos[i]) {
|
||||
const oldPath = report[`photo_path${i + 1}`];
|
||||
if (oldPath) {
|
||||
await imageUploadService.deleteFile(oldPath);
|
||||
}
|
||||
const savedPath = await imageUploadService.saveBase64Image(photos[i], 'issue');
|
||||
if (savedPath) {
|
||||
photoPaths[`photo_path${i + 1}`] = savedPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
factory_category_id,
|
||||
workplace_id,
|
||||
custom_location,
|
||||
issue_category_id,
|
||||
issue_item_id,
|
||||
additional_description,
|
||||
...photoPaths
|
||||
};
|
||||
|
||||
await workIssueModel.updateReport(id, updateData, req.user.user_id);
|
||||
res.json({ success: true, message: '신고가 수정되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('신고 수정 실패:', err);
|
||||
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
exports.deleteReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const report = await workIssueModel.getReportById(id);
|
||||
if (!report) {
|
||||
return res.status(404).json({ success: false, error: '신고를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 권한 확인
|
||||
const userLevel = req.user.access_level;
|
||||
const isOwner = report.reporter_id === req.user.user_id;
|
||||
const isManager = ['admin', 'system'].includes(userLevel);
|
||||
@@ -471,92 +363,74 @@ exports.deleteReport = async (req, res) => {
|
||||
return res.status(403).json({ success: false, error: '삭제 권한이 없습니다.' });
|
||||
}
|
||||
|
||||
workIssueModel.deleteReport(id, async (deleteErr, { result, photos }) => {
|
||||
if (deleteErr) {
|
||||
console.error('신고 삭제 실패:', deleteErr);
|
||||
return res.status(500).json({ success: false, error: '신고 삭제 실패' });
|
||||
}
|
||||
const { photos } = await workIssueModel.deleteReport(id);
|
||||
|
||||
// 사진 파일 삭제
|
||||
if (photos) {
|
||||
const allPhotos = [
|
||||
photos.photo_path1, photos.photo_path2, photos.photo_path3,
|
||||
photos.photo_path4, photos.photo_path5,
|
||||
photos.resolution_photo_path1, photos.resolution_photo_path2
|
||||
].filter(Boolean);
|
||||
await imageUploadService.deleteMultipleFiles(allPhotos);
|
||||
}
|
||||
if (photos) {
|
||||
const allPhotos = [
|
||||
photos.photo_path1, photos.photo_path2, photos.photo_path3,
|
||||
photos.photo_path4, photos.photo_path5,
|
||||
photos.resolution_photo_path1, photos.resolution_photo_path2
|
||||
].filter(Boolean);
|
||||
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 = (req, res) => {
|
||||
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 || '신고 접수 실패' });
|
||||
}
|
||||
exports.receiveReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await workIssueModel.receiveReport(id, req.user.user_id);
|
||||
res.json({ success: true, 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: '담당자는 필수입니다.' });
|
||||
} catch (err) {
|
||||
logger.error('신고 접수 실패:', err);
|
||||
res.status(400).json({ success: false, error: err.message || '신고 접수 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
workIssueModel.assignReport(id, {
|
||||
assigned_department,
|
||||
assigned_user_id,
|
||||
assigned_by: req.user.user_id
|
||||
}, (err, result) => {
|
||||
if (err) {
|
||||
console.error('담당자 배정 실패:', err);
|
||||
return res.status(400).json({ success: false, error: err.message || '담당자 배정 실패' });
|
||||
exports.assignReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { assigned_department, assigned_user_id } = req.body;
|
||||
|
||||
if (!assigned_user_id) {
|
||||
return res.status(400).json({ success: false, error: '담당자는 필수입니다.' });
|
||||
}
|
||||
|
||||
await workIssueModel.assignReport(id, {
|
||||
assigned_department,
|
||||
assigned_user_id,
|
||||
assigned_by: req.user.user_id
|
||||
});
|
||||
res.json({ success: true, message: '담당자가 배정되었습니다.' });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('담당자 배정 실패:', err);
|
||||
res.status(400).json({ success: false, error: err.message || '담당자 배정 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 처리 시작
|
||||
*/
|
||||
exports.startProcessing = (req, res) => {
|
||||
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 || '처리 시작 실패' });
|
||||
}
|
||||
exports.startProcessing = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await workIssueModel.startProcessing(id, req.user.user_id);
|
||||
res.json({ success: true, message: '처리가 시작되었습니다.' });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('처리 시작 실패:', err);
|
||||
res.status(400).json({ success: false, error: err.message || '처리 시작 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 처리 완료
|
||||
*/
|
||||
exports.completeReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { resolution_notes, resolution_photos = [] } = req.body;
|
||||
|
||||
// 완료 사진 저장
|
||||
let resolution_photo_path1 = 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');
|
||||
}
|
||||
|
||||
workIssueModel.completeReport(id, {
|
||||
await workIssueModel.completeReport(id, {
|
||||
resolution_notes,
|
||||
resolution_photo_path1,
|
||||
resolution_photo_path2,
|
||||
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) {
|
||||
console.error('처리 완료 에러:', error);
|
||||
res.status(500).json({ success: false, error: '서버 오류가 발생했습니다.' });
|
||||
res.json({ success: true, message: '처리가 완료되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('처리 완료 실패:', err);
|
||||
res.status(400).json({ success: false, error: err.message || '처리 완료 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 신고 종료
|
||||
*/
|
||||
exports.closeReport = (req, res) => {
|
||||
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 || '신고 종료 실패' });
|
||||
}
|
||||
exports.closeReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await workIssueModel.closeReport(id, req.user.user_id);
|
||||
res.json({ success: true, message: '신고가 종료되었습니다.' });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('신고 종료 실패:', err);
|
||||
res.status(400).json({ success: false, error: err.message || '신고 종료 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 상태 변경 이력 조회
|
||||
*/
|
||||
exports.getStatusLogs = (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
workIssueModel.getStatusLogs(id, (err, logs) => {
|
||||
if (err) {
|
||||
console.error('상태 이력 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '상태 이력 조회 실패' });
|
||||
}
|
||||
exports.getStatusLogs = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const logs = await workIssueModel.getStatusLogs(id);
|
||||
res.json({ success: true, data: logs });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('상태 이력 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '상태 이력 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 통계 ====================
|
||||
|
||||
/**
|
||||
* 통계 요약
|
||||
*/
|
||||
exports.getStatsSummary = (req, res) => {
|
||||
const filters = {
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
factory_category_id: req.query.factory_category_id
|
||||
};
|
||||
|
||||
workIssueModel.getStatsSummary(filters, (err, stats) => {
|
||||
if (err) {
|
||||
console.error('통계 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
exports.getStatsSummary = async (req, res) => {
|
||||
try {
|
||||
const filters = {
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
factory_category_id: req.query.factory_category_id
|
||||
};
|
||||
const stats = await workIssueModel.getStatsSummary(filters);
|
||||
res.json({ success: true, data: stats });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('통계 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리별 통계
|
||||
*/
|
||||
exports.getStatsByCategory = (req, res) => {
|
||||
const filters = {
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date
|
||||
};
|
||||
|
||||
workIssueModel.getStatsByCategory(filters, (err, stats) => {
|
||||
if (err) {
|
||||
console.error('카테고리별 통계 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
exports.getStatsByCategory = async (req, res) => {
|
||||
try {
|
||||
const filters = {
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date
|
||||
};
|
||||
const stats = await workIssueModel.getStatsByCategory(filters);
|
||||
res.json({ success: true, data: stats });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('카테고리별 통계 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업장별 통계
|
||||
*/
|
||||
exports.getStatsByWorkplace = (req, res) => {
|
||||
const filters = {
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
factory_category_id: req.query.factory_category_id
|
||||
};
|
||||
|
||||
workIssueModel.getStatsByWorkplace(filters, (err, stats) => {
|
||||
if (err) {
|
||||
console.error('작업장별 통계 조회 실패:', err);
|
||||
return res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
exports.getStatsByWorkplace = async (req, res) => {
|
||||
try {
|
||||
const filters = {
|
||||
start_date: req.query.start_date,
|
||||
end_date: req.query.end_date,
|
||||
factory_category_id: req.query.factory_category_id
|
||||
};
|
||||
const stats = await workIssueModel.getStatsByWorkplace(filters);
|
||||
res.json({ success: true, data: stats });
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('작업장별 통계 조회 실패:', err);
|
||||
res.status(500).json({ success: false, error: '통계 조회 실패' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,15 +8,12 @@
|
||||
*/
|
||||
|
||||
const workplaceModel = require('../models/workplaceModel');
|
||||
const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors');
|
||||
const { ValidationError, NotFoundError } = require('../utils/errors');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// ==================== 카테고리(공장) 관련 ====================
|
||||
|
||||
/**
|
||||
* 카테고리 생성
|
||||
*/
|
||||
exports.createCategory = asyncHandler(async (req, res) => {
|
||||
const categoryData = req.body;
|
||||
|
||||
@@ -26,12 +23,7 @@ exports.createCategory = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('카테고리 생성 요청', { name: categoryData.category_name });
|
||||
|
||||
const id = await new Promise((resolve, reject) => {
|
||||
workplaceModel.createCategory(categoryData, (err, lastID) => {
|
||||
if (err) reject(new DatabaseError('카테고리 생성 중 오류가 발생했습니다'));
|
||||
else resolve(lastID);
|
||||
});
|
||||
});
|
||||
const id = await workplaceModel.createCategory(categoryData);
|
||||
|
||||
logger.info('카테고리 생성 성공', { category_id: id });
|
||||
|
||||
@@ -42,16 +34,8 @@ exports.createCategory = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 전체 카테고리 조회
|
||||
*/
|
||||
exports.getAllCategories = asyncHandler(async (req, res) => {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getAllCategories((err, data) => {
|
||||
if (err) reject(new DatabaseError('카테고리 목록 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const rows = await workplaceModel.getAllCategories();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -60,16 +44,8 @@ exports.getAllCategories = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 활성 카테고리만 조회
|
||||
*/
|
||||
exports.getActiveCategories = asyncHandler(async (req, res) => {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getActiveCategories((err, data) => {
|
||||
if (err) reject(new DatabaseError('활성 카테고리 목록 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const rows = await workplaceModel.getActiveCategories();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -78,18 +54,9 @@ exports.getActiveCategories = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 단일 카테고리 조회
|
||||
*/
|
||||
exports.getCategoryById = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.params.id;
|
||||
|
||||
const category = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getCategoryById(categoryId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const category = await workplaceModel.getCategoryById(categoryId);
|
||||
|
||||
if (!category) {
|
||||
throw new NotFoundError('카테고리를 찾을 수 없습니다');
|
||||
@@ -102,9 +69,6 @@ exports.getCategoryById = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 카테고리 수정
|
||||
*/
|
||||
exports.updateCategory = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.params.id;
|
||||
const categoryData = req.body;
|
||||
@@ -115,19 +79,11 @@ exports.updateCategory = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('카테고리 수정 요청', { category_id: categoryId });
|
||||
|
||||
// 기존 카테고리 정보 가져오기
|
||||
const existingCategory = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getCategoryById(categoryId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const existingCategory = await workplaceModel.getCategoryById(categoryId);
|
||||
if (!existingCategory) {
|
||||
throw new NotFoundError('카테고리를 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// layout_image가 요청에 없거나 null이면 기존 값 보존
|
||||
const updateData = {
|
||||
...categoryData,
|
||||
layout_image: (categoryData.layout_image !== undefined && categoryData.layout_image !== null)
|
||||
@@ -135,12 +91,7 @@ exports.updateCategory = asyncHandler(async (req, res) => {
|
||||
: existingCategory.layout_image
|
||||
};
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.updateCategory(categoryId, updateData, (err, result) => {
|
||||
if (err) reject(new DatabaseError('카테고리 수정 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.updateCategory(categoryId, updateData);
|
||||
|
||||
logger.info('카테고리 수정 성공', { category_id: categoryId });
|
||||
|
||||
@@ -150,20 +101,12 @@ exports.updateCategory = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 카테고리 삭제
|
||||
*/
|
||||
exports.deleteCategory = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.params.id;
|
||||
|
||||
logger.info('카테고리 삭제 요청', { category_id: categoryId });
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.deleteCategory(categoryId, (err, result) => {
|
||||
if (err) reject(new DatabaseError('카테고리 삭제 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.deleteCategory(categoryId);
|
||||
|
||||
logger.info('카테고리 삭제 성공', { category_id: categoryId });
|
||||
|
||||
@@ -175,9 +118,6 @@ exports.deleteCategory = asyncHandler(async (req, res) => {
|
||||
|
||||
// ==================== 작업장 관련 ====================
|
||||
|
||||
/**
|
||||
* 작업장 생성
|
||||
*/
|
||||
exports.createWorkplace = asyncHandler(async (req, res) => {
|
||||
const workplaceData = req.body;
|
||||
|
||||
@@ -187,12 +127,7 @@ exports.createWorkplace = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('작업장 생성 요청', { name: workplaceData.workplace_name });
|
||||
|
||||
const id = await new Promise((resolve, reject) => {
|
||||
workplaceModel.createWorkplace(workplaceData, (err, lastID) => {
|
||||
if (err) reject(new DatabaseError('작업장 생성 중 오류가 발생했습니다'));
|
||||
else resolve(lastID);
|
||||
});
|
||||
});
|
||||
const id = await workplaceModel.createWorkplace(workplaceData);
|
||||
|
||||
logger.info('작업장 생성 성공', { workplace_id: id });
|
||||
|
||||
@@ -203,35 +138,12 @@ exports.createWorkplace = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 전체 작업장 조회
|
||||
*/
|
||||
exports.getAllWorkplaces = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.query.category_id;
|
||||
|
||||
// 카테고리별 필터링
|
||||
if (categoryId) {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
const rows = categoryId
|
||||
? await workplaceModel.getWorkplacesByCategory(categoryId)
|
||||
: await workplaceModel.getAllWorkplaces();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -240,16 +152,8 @@ exports.getAllWorkplaces = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 활성 작업장만 조회
|
||||
*/
|
||||
exports.getActiveWorkplaces = asyncHandler(async (req, res) => {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getActiveWorkplaces((err, data) => {
|
||||
if (err) reject(new DatabaseError('활성 작업장 목록 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const rows = await workplaceModel.getActiveWorkplaces();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -258,18 +162,9 @@ exports.getActiveWorkplaces = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 단일 작업장 조회
|
||||
*/
|
||||
exports.getWorkplaceById = asyncHandler(async (req, res) => {
|
||||
const workplaceId = req.params.id;
|
||||
|
||||
const workplace = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const workplace = await workplaceModel.getWorkplaceById(workplaceId);
|
||||
|
||||
if (!workplace) {
|
||||
throw new NotFoundError('작업장을 찾을 수 없습니다');
|
||||
@@ -282,9 +177,6 @@ exports.getWorkplaceById = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 작업장 수정
|
||||
*/
|
||||
exports.updateWorkplace = asyncHandler(async (req, res) => {
|
||||
const workplaceId = req.params.id;
|
||||
const workplaceData = req.body;
|
||||
@@ -295,19 +187,11 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('작업장 수정 요청', { workplace_id: workplaceId });
|
||||
|
||||
// 기존 작업장 정보 가져오기
|
||||
const existingWorkplace = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const existingWorkplace = await workplaceModel.getWorkplaceById(workplaceId);
|
||||
if (!existingWorkplace) {
|
||||
throw new NotFoundError('작업장을 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// layout_image가 요청에 없거나 null이면 기존 값 보존
|
||||
const updateData = {
|
||||
...workplaceData,
|
||||
layout_image: (workplaceData.layout_image !== undefined && workplaceData.layout_image !== null)
|
||||
@@ -315,12 +199,7 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
|
||||
: existingWorkplace.layout_image
|
||||
};
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.updateWorkplace(workplaceId, updateData, (err, result) => {
|
||||
if (err) reject(new DatabaseError('작업장 수정 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.updateWorkplace(workplaceId, updateData);
|
||||
|
||||
logger.info('작업장 수정 성공', { workplace_id: workplaceId });
|
||||
|
||||
@@ -330,20 +209,12 @@ exports.updateWorkplace = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 작업장 삭제
|
||||
*/
|
||||
exports.deleteWorkplace = asyncHandler(async (req, res) => {
|
||||
const workplaceId = req.params.id;
|
||||
|
||||
logger.info('작업장 삭제 요청', { workplace_id: workplaceId });
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.deleteWorkplace(workplaceId, (err, result) => {
|
||||
if (err) reject(new DatabaseError('작업장 삭제 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.deleteWorkplace(workplaceId);
|
||||
|
||||
logger.info('작업장 삭제 성공', { workplace_id: workplaceId });
|
||||
|
||||
@@ -355,9 +226,6 @@ exports.deleteWorkplace = asyncHandler(async (req, res) => {
|
||||
|
||||
// ==================== 작업장 지도 영역 관련 ====================
|
||||
|
||||
/**
|
||||
* 카테고리 레이아웃 이미지 업로드
|
||||
*/
|
||||
exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.params.id;
|
||||
|
||||
@@ -369,19 +237,11 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('카테고리 레이아웃 이미지 업로드 요청', { category_id: categoryId, path: imagePath });
|
||||
|
||||
// 현재 카테고리 정보 가져오기
|
||||
const category = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getCategoryById(categoryId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const category = await workplaceModel.getCategoryById(categoryId);
|
||||
if (!category) {
|
||||
throw new NotFoundError('카테고리를 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// 카테고리 정보 업데이트 (이미지 경로만 변경)
|
||||
const updatedData = {
|
||||
category_name: category.category_name,
|
||||
description: category.description,
|
||||
@@ -390,12 +250,7 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
|
||||
layout_image: imagePath
|
||||
};
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.updateCategory(categoryId, updatedData, (err, result) => {
|
||||
if (err) reject(new DatabaseError('이미지 경로 저장 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.updateCategory(categoryId, updatedData);
|
||||
|
||||
logger.info('레이아웃 이미지 업로드 성공', { category_id: categoryId });
|
||||
|
||||
@@ -406,9 +261,6 @@ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 작업장 레이아웃 이미지 업로드
|
||||
*/
|
||||
exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
|
||||
const workplaceId = req.params.id;
|
||||
|
||||
@@ -420,19 +272,11 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('작업장 레이아웃 이미지 업로드 요청', { workplace_id: workplaceId, path: imagePath });
|
||||
|
||||
// 현재 작업장 정보 가져오기
|
||||
const workplace = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getWorkplaceById(workplaceId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const workplace = await workplaceModel.getWorkplaceById(workplaceId);
|
||||
if (!workplace) {
|
||||
throw new NotFoundError('작업장을 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// 작업장 정보 업데이트 (이미지 경로만 변경)
|
||||
const updatedData = {
|
||||
workplace_name: workplace.workplace_name,
|
||||
category_id: workplace.category_id,
|
||||
@@ -443,12 +287,7 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
|
||||
layout_image: imagePath
|
||||
};
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.updateWorkplace(workplaceId, updatedData, (err, result) => {
|
||||
if (err) reject(new DatabaseError('이미지 경로 저장 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.updateWorkplace(workplaceId, updatedData);
|
||||
|
||||
logger.info('작업장 레이아웃 이미지 업로드 성공', { workplace_id: workplaceId });
|
||||
|
||||
@@ -459,9 +298,6 @@ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 지도 영역 생성
|
||||
*/
|
||||
exports.createMapRegion = asyncHandler(async (req, res) => {
|
||||
const regionData = req.body;
|
||||
|
||||
@@ -471,12 +307,7 @@ exports.createMapRegion = asyncHandler(async (req, res) => {
|
||||
|
||||
logger.info('지도 영역 생성 요청', { workplace_id: regionData.workplace_id });
|
||||
|
||||
const id = await new Promise((resolve, reject) => {
|
||||
workplaceModel.createMapRegion(regionData, (err, lastID) => {
|
||||
if (err) reject(new DatabaseError('지도 영역 생성 중 오류가 발생했습니다'));
|
||||
else resolve(lastID);
|
||||
});
|
||||
});
|
||||
const id = await workplaceModel.createMapRegion(regionData);
|
||||
|
||||
logger.info('지도 영역 생성 성공', { region_id: id });
|
||||
|
||||
@@ -487,18 +318,9 @@ exports.createMapRegion = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 카테고리별 지도 영역 조회 (작업장 정보 포함)
|
||||
*/
|
||||
exports.getMapRegionsByCategory = asyncHandler(async (req, res) => {
|
||||
const categoryId = req.params.categoryId;
|
||||
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getMapRegionsByCategory(categoryId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('지도 영역 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const rows = await workplaceModel.getMapRegionsByCategory(categoryId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -507,18 +329,9 @@ exports.getMapRegionsByCategory = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 작업장별 지도 영역 조회
|
||||
*/
|
||||
exports.getMapRegionByWorkplace = asyncHandler(async (req, res) => {
|
||||
const workplaceId = req.params.workplaceId;
|
||||
|
||||
const region = await new Promise((resolve, reject) => {
|
||||
workplaceModel.getMapRegionByWorkplace(workplaceId, (err, data) => {
|
||||
if (err) reject(new DatabaseError('지도 영역 조회 중 오류가 발생했습니다'));
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const region = await workplaceModel.getMapRegionByWorkplace(workplaceId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -527,21 +340,13 @@ exports.getMapRegionByWorkplace = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 지도 영역 수정
|
||||
*/
|
||||
exports.updateMapRegion = asyncHandler(async (req, res) => {
|
||||
const regionId = req.params.id;
|
||||
const regionData = req.body;
|
||||
|
||||
logger.info('지도 영역 수정 요청', { region_id: regionId });
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.updateMapRegion(regionId, regionData, (err, result) => {
|
||||
if (err) reject(new DatabaseError('지도 영역 수정 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.updateMapRegion(regionId, regionData);
|
||||
|
||||
logger.info('지도 영역 수정 성공', { region_id: regionId });
|
||||
|
||||
@@ -551,20 +356,12 @@ exports.updateMapRegion = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 지도 영역 삭제
|
||||
*/
|
||||
exports.deleteMapRegion = asyncHandler(async (req, res) => {
|
||||
const regionId = req.params.id;
|
||||
|
||||
logger.info('지도 영역 삭제 요청', { region_id: regionId });
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
workplaceModel.deleteMapRegion(regionId, (err, result) => {
|
||||
if (err) reject(new DatabaseError('지도 영역 삭제 중 오류가 발생했습니다'));
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
await workplaceModel.deleteMapRegion(regionId);
|
||||
|
||||
logger.info('지도 영역 삭제 성공', { region_id: regionId });
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ const { getDb } = require('../dbPool');
|
||||
|
||||
/**
|
||||
* 1. 여러 개의 이슈 보고서를 트랜잭션으로 생성합니다.
|
||||
* @param {Array<object>} reports - 생성할 보고서 데이터 배열
|
||||
* @returns {Promise<Array<number>>} - 삽입된 ID 배열
|
||||
*/
|
||||
const createMany = async (reports) => {
|
||||
const db = await getDb();
|
||||
@@ -13,7 +11,7 @@ const createMany = async (reports) => {
|
||||
|
||||
const insertedIds = [];
|
||||
const sql = `
|
||||
INSERT INTO DailyIssueReports
|
||||
INSERT INTO DailyIssueReports
|
||||
(date, worker_id, project_id, start_time, end_time, issue_type_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
@@ -36,119 +34,71 @@ const createMany = async (reports) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 2. 특정 날짜의 전체 이슈 목록 조회 (Promise 기반)
|
||||
* 2. 특정 날짜의 전체 이슈 목록 조회
|
||||
*/
|
||||
const getAllByDate = async (date) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
d.id, d.date, w.worker_name, p.project_name, d.start_time, d.end_time,
|
||||
t.category, t.subcategory, d.description
|
||||
FROM DailyIssueReports d
|
||||
LEFT JOIN workers w ON d.worker_id = w.worker_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
|
||||
WHERE d.date = ?
|
||||
ORDER BY d.start_time ASC`,
|
||||
[date]
|
||||
);
|
||||
return rows;
|
||||
} catch (err) {
|
||||
console.error(`[Model] ${date} 이슈 목록 조회 오류:`, err);
|
||||
throw new Error('데이터베이스에서 이슈 목록을 조회하는 중 오류가 발생했습니다.');
|
||||
}
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
d.id, d.date, w.worker_name, p.project_name, d.start_time, d.end_time,
|
||||
t.category, t.subcategory, d.description
|
||||
FROM DailyIssueReports d
|
||||
LEFT JOIN workers w ON d.worker_id = w.worker_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
|
||||
WHERE d.date = ?
|
||||
ORDER BY d.start_time ASC`,
|
||||
[date]
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 3. 단일 조회 (선택사항: 컨트롤러에서 사용 중)
|
||||
* 3. 단일 조회
|
||||
*/
|
||||
const getById = async (id, callback) => {
|
||||
try {
|
||||
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]);
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getById = async (id) => {
|
||||
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]);
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 4. 수정
|
||||
*/
|
||||
const update = async (id, data, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const update = async (id, data) => {
|
||||
const db = await getDb();
|
||||
|
||||
const fields = [];
|
||||
const values = [];
|
||||
const fields = [];
|
||||
const values = [];
|
||||
|
||||
for (const key in data) {
|
||||
fields.push(`${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);
|
||||
for (const key in data) {
|
||||
fields.push(`${key} = ?`);
|
||||
values.push(data[key]);
|
||||
}
|
||||
|
||||
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) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`DELETE FROM DailyIssueReports WHERE id = ?`, [id]);
|
||||
return result.affectedRows;
|
||||
} catch (err) {
|
||||
console.error(`[Model] 이슈(id: ${id}) 삭제 오류:`, err);
|
||||
throw new Error('데이터베이스에서 이슈를 삭제하는 중 오류가 발생했습니다.');
|
||||
}
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`DELETE FROM DailyIssueReports WHERE id = ?`, [id]);
|
||||
return result.affectedRows;
|
||||
};
|
||||
|
||||
// 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 = {
|
||||
createMany, // 신규
|
||||
createMany,
|
||||
getAllByDate,
|
||||
remove,
|
||||
// 레거시 호환성을 위해 V1 함수들 임시 유지
|
||||
create: (report, callback) => createMany([report]).then(ids => callback(null, ids[0])).catch(err => callback(err)),
|
||||
getById,
|
||||
update,
|
||||
};
|
||||
remove
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -6,11 +6,10 @@ const TbmTransferModel = {
|
||||
* 작업자 이동 실행 (보내기/빼오기)
|
||||
* 트랜잭션: source work_hours 업데이트 + dest INSERT + 로그 INSERT
|
||||
*/
|
||||
createTransfer: async (transferData, callback) => {
|
||||
let conn;
|
||||
async createTransfer(transferData) {
|
||||
const db = await getDb();
|
||||
const conn = await db.getConnection();
|
||||
try {
|
||||
const db = await getDb();
|
||||
conn = await db.getConnection();
|
||||
await conn.beginTransaction();
|
||||
|
||||
const {
|
||||
@@ -27,8 +26,7 @@ const TbmTransferModel = {
|
||||
|
||||
if (sourceRows.length === 0) {
|
||||
await conn.rollback();
|
||||
conn.release();
|
||||
return callback(null, { success: false, message: '원본 세션에서 해당 작업자를 찾을 수 없습니다.' });
|
||||
return { success: false, message: '원본 세션에서 해당 작업자를 찾을 수 없습니다.' };
|
||||
}
|
||||
|
||||
const currentHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours);
|
||||
@@ -36,8 +34,7 @@ const TbmTransferModel = {
|
||||
|
||||
if (newSourceHours < 0) {
|
||||
await conn.rollback();
|
||||
conn.release();
|
||||
return callback(null, { success: false, message: '이동 시간이 현재 배정 시간보다 큽니다.' });
|
||||
return { success: false, message: '이동 시간이 현재 배정 시간보다 큽니다.' };
|
||||
}
|
||||
|
||||
await conn.query(
|
||||
@@ -52,14 +49,12 @@ const TbmTransferModel = {
|
||||
);
|
||||
|
||||
if (destRows.length > 0) {
|
||||
// 이미 있으면 시간만 추가
|
||||
const existingHours = destRows[0].work_hours === null ? 8 : parseFloat(destRows[0].work_hours);
|
||||
await conn.query(
|
||||
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?',
|
||||
[existingHours + parseFloat(hours), dest_session_id, worker_id]
|
||||
);
|
||||
} else {
|
||||
// 새로 INSERT
|
||||
await conn.query(
|
||||
`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)
|
||||
@@ -89,7 +84,6 @@ const TbmTransferModel = {
|
||||
const totalHours = totalRows[0] ? parseFloat(totalRows[0].total_hours) : 0;
|
||||
|
||||
await conn.commit();
|
||||
conn.release();
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
@@ -101,24 +95,22 @@ const TbmTransferModel = {
|
||||
result.warning = `해당 작업자의 당일 합계가 ${totalHours}h입니다 (8h 초과).`;
|
||||
}
|
||||
|
||||
callback(null, result);
|
||||
return result;
|
||||
} catch (err) {
|
||||
if (conn) {
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
conn.release();
|
||||
}
|
||||
callback(err);
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 이동 취소 (원복)
|
||||
*/
|
||||
cancelTransfer: async (transferId, callback) => {
|
||||
let conn;
|
||||
async cancelTransfer(transferId) {
|
||||
const db = await getDb();
|
||||
const conn = await db.getConnection();
|
||||
try {
|
||||
const db = await getDb();
|
||||
conn = await db.getConnection();
|
||||
await conn.beginTransaction();
|
||||
|
||||
// 1. 이동 로그 조회
|
||||
@@ -129,8 +121,7 @@ const TbmTransferModel = {
|
||||
|
||||
if (transfers.length === 0) {
|
||||
await conn.rollback();
|
||||
conn.release();
|
||||
return callback(null, { success: false, message: '이동 기록을 찾을 수 없습니다.' });
|
||||
return { success: false, message: '이동 기록을 찾을 수 없습니다.' };
|
||||
}
|
||||
|
||||
const t = transfers[0];
|
||||
@@ -146,7 +137,6 @@ const TbmTransferModel = {
|
||||
const newDestHours = destHours - parseFloat(t.hours);
|
||||
|
||||
if (newDestHours <= 0) {
|
||||
// 삭제
|
||||
await conn.query(
|
||||
'DELETE FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?',
|
||||
[t.dest_session_id, t.worker_id]
|
||||
@@ -168,7 +158,6 @@ const TbmTransferModel = {
|
||||
if (sourceRows.length > 0) {
|
||||
const sourceHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours);
|
||||
const restoredHours = sourceHours + parseFloat(t.hours);
|
||||
// 8이면 NULL로 복원 (종일)
|
||||
await conn.query(
|
||||
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND 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.commit();
|
||||
conn.release();
|
||||
|
||||
callback(null, { success: true });
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
if (conn) {
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
conn.release();
|
||||
}
|
||||
callback(err);
|
||||
try { await conn.rollback(); } catch (e) {}
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 당일 이동 내역 조회
|
||||
*/
|
||||
getTransfersByDate: async (date, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
SELECT
|
||||
t.*,
|
||||
w.worker_name,
|
||||
w.job_type,
|
||||
sl.worker_name as source_leader_name,
|
||||
dl.worker_name as dest_leader_name,
|
||||
u.name as initiated_by_name
|
||||
FROM tbm_transfers t
|
||||
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 workers sl ON ss.leader_id = sl.worker_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 sso_users u ON t.initiated_by = u.user_id
|
||||
WHERE t.transfer_date = ?
|
||||
ORDER BY t.created_at DESC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [date]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
async getTransfersByDate(date) {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
SELECT
|
||||
t.*,
|
||||
w.worker_name,
|
||||
w.job_type,
|
||||
sl.worker_name as source_leader_name,
|
||||
dl.worker_name as dest_leader_name,
|
||||
u.name as initiated_by_name
|
||||
FROM tbm_transfers t
|
||||
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 workers sl ON ss.leader_id = sl.worker_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 sso_users u ON t.initiated_by = u.user_id
|
||||
WHERE t.transfer_date = ?
|
||||
ORDER BY t.created_at DESC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [date]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 당일 전 작업자 배정 현황 조회
|
||||
*/
|
||||
getWorkerAssignmentsByDate: async (date, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
async getWorkerAssignmentsByDate(date) {
|
||||
const db = await getDb();
|
||||
|
||||
// 1. 해당 날짜의 모든 배정 가져오기
|
||||
const [assignments] = await db.query(`
|
||||
SELECT
|
||||
ta.worker_id,
|
||||
ta.session_id,
|
||||
ta.work_hours,
|
||||
w.worker_name,
|
||||
w.job_type,
|
||||
s.leader_id,
|
||||
lw.worker_name as leader_name,
|
||||
s.status as session_status
|
||||
FROM tbm_team_assignments ta
|
||||
INNER JOIN tbm_sessions s ON ta.session_id = s.session_id
|
||||
INNER JOIN workers w ON ta.worker_id = w.worker_id
|
||||
LEFT JOIN workers lw ON s.leader_id = lw.worker_id
|
||||
WHERE s.session_date = ?
|
||||
ORDER BY w.worker_name
|
||||
`, [date]);
|
||||
// 1. 해당 날짜의 모든 배정 가져오기
|
||||
const [assignments] = await db.query(`
|
||||
SELECT
|
||||
ta.worker_id,
|
||||
ta.session_id,
|
||||
ta.work_hours,
|
||||
w.worker_name,
|
||||
w.job_type,
|
||||
s.leader_id,
|
||||
lw.worker_name as leader_name,
|
||||
s.status as session_status
|
||||
FROM tbm_team_assignments ta
|
||||
INNER JOIN tbm_sessions s ON ta.session_id = s.session_id
|
||||
INNER JOIN workers w ON ta.worker_id = w.worker_id
|
||||
LEFT JOIN workers lw ON s.leader_id = lw.worker_id
|
||||
WHERE s.session_date = ?
|
||||
ORDER BY w.worker_name
|
||||
`, [date]);
|
||||
|
||||
// 2. 모든 작업자 가져오기 (배정 안 된 사람도 포함)
|
||||
const [allWorkers] = await db.query(
|
||||
"SELECT worker_id, worker_name, job_type FROM workers WHERE status = 'active' AND department = '생산' ORDER BY worker_name"
|
||||
);
|
||||
// 2. 모든 작업자 가져오기 (배정 안 된 사람도 포함)
|
||||
const [allWorkers] = await db.query(
|
||||
"SELECT worker_id, worker_name, job_type FROM workers WHERE status = 'active' AND department = '생산' ORDER BY worker_name"
|
||||
);
|
||||
|
||||
// 3. 작업자별 배정 현황 구성
|
||||
const workerMap = {};
|
||||
allWorkers.forEach(w => {
|
||||
workerMap[w.worker_id] = {
|
||||
worker_id: w.worker_id,
|
||||
worker_name: w.worker_name,
|
||||
job_type: w.job_type,
|
||||
sessions: [],
|
||||
total_hours: 0,
|
||||
available: true
|
||||
};
|
||||
});
|
||||
// 3. 작업자별 배정 현황 구성
|
||||
const workerMap = {};
|
||||
allWorkers.forEach(w => {
|
||||
workerMap[w.worker_id] = {
|
||||
worker_id: w.worker_id,
|
||||
worker_name: w.worker_name,
|
||||
job_type: w.job_type,
|
||||
sessions: [],
|
||||
total_hours: 0,
|
||||
available: true
|
||||
};
|
||||
});
|
||||
|
||||
assignments.forEach(a => {
|
||||
const hours = a.work_hours === null ? 8 : parseFloat(a.work_hours);
|
||||
if (workerMap[a.worker_id]) {
|
||||
workerMap[a.worker_id].sessions.push({
|
||||
session_id: a.session_id,
|
||||
leader_name: a.leader_name,
|
||||
work_hours: hours,
|
||||
session_status: a.session_status
|
||||
});
|
||||
workerMap[a.worker_id].total_hours += hours;
|
||||
}
|
||||
});
|
||||
assignments.forEach(a => {
|
||||
const hours = a.work_hours === null ? 8 : parseFloat(a.work_hours);
|
||||
if (workerMap[a.worker_id]) {
|
||||
workerMap[a.worker_id].sessions.push({
|
||||
session_id: a.session_id,
|
||||
leader_name: a.leader_name,
|
||||
work_hours: hours,
|
||||
session_status: a.session_status
|
||||
});
|
||||
workerMap[a.worker_id].total_hours += hours;
|
||||
}
|
||||
});
|
||||
|
||||
// available 판단
|
||||
Object.values(workerMap).forEach(w => {
|
||||
w.available = w.total_hours < 8;
|
||||
});
|
||||
Object.values(workerMap).forEach(w => {
|
||||
w.available = w.total_hours < 8;
|
||||
});
|
||||
|
||||
callback(null, Object.values(workerMap));
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return Object.values(workerMap);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,251 +9,190 @@ const vacationBalanceModel = {
|
||||
/**
|
||||
* 특정 작업자의 모든 휴가 잔액 조회 (특정 연도)
|
||||
*/
|
||||
async getByWorkerAndYear(workerId, year, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
vbd.*,
|
||||
vt.type_name,
|
||||
vt.type_code,
|
||||
vt.priority,
|
||||
vt.is_special
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.worker_id = ? AND vbd.year = ?
|
||||
ORDER BY vt.priority ASC, vt.type_name ASC
|
||||
`;
|
||||
const [rows] = await db.query(query, [workerId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async getByWorkerAndYear(workerId, year) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
vbd.*,
|
||||
vt.type_name,
|
||||
vt.type_code,
|
||||
vt.priority,
|
||||
vt.is_special
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.worker_id = ? AND vbd.year = ?
|
||||
ORDER BY vt.priority ASC, vt.type_name ASC
|
||||
`, [workerId, year]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 특정 작업자의 특정 휴가 유형 잔액 조회
|
||||
*/
|
||||
async getByWorkerTypeYear(workerId, vacationTypeId, year, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
vbd.*,
|
||||
vt.type_name,
|
||||
vt.type_code
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.worker_id = ?
|
||||
AND vbd.vacation_type_id = ?
|
||||
AND vbd.year = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [workerId, vacationTypeId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async getByWorkerTypeYear(workerId, vacationTypeId, year) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
vbd.*,
|
||||
vt.type_name,
|
||||
vt.type_code
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.worker_id = ?
|
||||
AND vbd.vacation_type_id = ?
|
||||
AND vbd.year = ?
|
||||
`, [workerId, vacationTypeId, year]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 모든 작업자의 휴가 잔액 조회 (특정 연도)
|
||||
* - 연간 연차 현황 차트용
|
||||
*/
|
||||
async getAllByYear(year, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
vbd.*,
|
||||
w.worker_name,
|
||||
w.employment_status,
|
||||
vt.type_name,
|
||||
vt.type_code,
|
||||
vt.priority
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN workers w ON vbd.worker_id = w.worker_id
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.year = ?
|
||||
AND w.employment_status = 'employed'
|
||||
ORDER BY w.worker_name ASC, vt.priority ASC
|
||||
`;
|
||||
const [rows] = await db.query(query, [year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async getAllByYear(year) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
vbd.*,
|
||||
w.worker_name,
|
||||
w.employment_status,
|
||||
vt.type_name,
|
||||
vt.type_code,
|
||||
vt.priority
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN workers w ON vbd.worker_id = w.worker_id
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.year = ?
|
||||
AND w.employment_status = 'employed'
|
||||
ORDER BY w.worker_name ASC, vt.priority ASC
|
||||
`, [year]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 잔액 생성
|
||||
*/
|
||||
async create(balanceData, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `INSERT INTO vacation_balance_details SET ?`;
|
||||
const [rows] = await db.query(query, balanceData);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async create(balanceData) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`INSERT INTO vacation_balance_details SET ?`, balanceData);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 잔액 수정
|
||||
*/
|
||||
async update(id, updateData, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `UPDATE vacation_balance_details SET ? WHERE id = ?`;
|
||||
const [rows] = await db.query(query, [updateData, id]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async update(id, updateData) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`UPDATE vacation_balance_details SET ? WHERE id = ?`, [updateData, id]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 잔액 삭제
|
||||
*/
|
||||
async delete(id, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `DELETE FROM vacation_balance_details WHERE id = ?`;
|
||||
const [rows] = await db.query(query, [id]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async delete(id) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`DELETE FROM vacation_balance_details WHERE id = ?`, [id]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업자의 휴가 사용 일수 업데이트 (차감)
|
||||
* - 휴가 신청 승인 시 호출
|
||||
*/
|
||||
async deductDays(workerId, vacationTypeId, year, daysToDeduct, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
UPDATE vacation_balance_details
|
||||
SET used_days = used_days + ?,
|
||||
updated_at = NOW()
|
||||
WHERE worker_id = ?
|
||||
AND vacation_type_id = ?
|
||||
AND year = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [daysToDeduct, workerId, vacationTypeId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async deductDays(workerId, vacationTypeId, year, daysToDeduct) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`
|
||||
UPDATE vacation_balance_details
|
||||
SET used_days = used_days + ?,
|
||||
updated_at = NOW()
|
||||
WHERE worker_id = ?
|
||||
AND vacation_type_id = ?
|
||||
AND year = ?
|
||||
`, [daysToDeduct, workerId, vacationTypeId, year]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업자의 휴가 사용 일수 복구 (취소)
|
||||
* - 휴가 신청 취소/거부 시 호출
|
||||
*/
|
||||
async restoreDays(workerId, vacationTypeId, year, daysToRestore, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
UPDATE vacation_balance_details
|
||||
SET used_days = GREATEST(0, used_days - ?),
|
||||
updated_at = NOW()
|
||||
WHERE worker_id = ?
|
||||
AND vacation_type_id = ?
|
||||
AND year = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [daysToRestore, workerId, vacationTypeId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async restoreDays(workerId, vacationTypeId, year, daysToRestore) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`
|
||||
UPDATE vacation_balance_details
|
||||
SET used_days = GREATEST(0, used_days - ?),
|
||||
updated_at = NOW()
|
||||
WHERE worker_id = ?
|
||||
AND vacation_type_id = ?
|
||||
AND year = ?
|
||||
`, [daysToRestore, workerId, vacationTypeId, year]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 특정 작업자의 사용 가능한 휴가 일수 확인
|
||||
* - 우선순위가 높은 순서대로 차감 가능 여부 확인
|
||||
*/
|
||||
async getAvailableVacationDays(workerId, year, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
vbd.id,
|
||||
vbd.vacation_type_id,
|
||||
vt.type_name,
|
||||
vt.type_code,
|
||||
vt.priority,
|
||||
vbd.total_days,
|
||||
vbd.used_days,
|
||||
vbd.remaining_days
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.worker_id = ?
|
||||
AND vbd.year = ?
|
||||
AND vbd.remaining_days > 0
|
||||
ORDER BY vt.priority ASC
|
||||
`;
|
||||
const [rows] = await db.query(query, [workerId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async getAvailableVacationDays(workerId, year) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
vbd.id,
|
||||
vbd.vacation_type_id,
|
||||
vt.type_name,
|
||||
vt.type_code,
|
||||
vt.priority,
|
||||
vbd.total_days,
|
||||
vbd.used_days,
|
||||
vbd.remaining_days
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.worker_id = ?
|
||||
AND vbd.year = ?
|
||||
AND vbd.remaining_days > 0
|
||||
ORDER BY vt.priority ASC
|
||||
`, [workerId, year]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업자별 휴가 잔액 일괄 생성 (연도별)
|
||||
* - 매년 초 또는 입사 시 사용
|
||||
*/
|
||||
async bulkCreate(balances, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
|
||||
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);
|
||||
async bulkCreate(balances) {
|
||||
if (!balances || balances.length === 0) {
|
||||
throw new 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) {
|
||||
const hire = new Date(hireDate);
|
||||
const targetDate = new Date(targetYear, 0, 1);
|
||||
|
||||
// 근속 월수 계산
|
||||
const monthsDiff = (targetDate.getFullYear() - hire.getFullYear()) * 12
|
||||
+ (targetDate.getMonth() - hire.getMonth());
|
||||
|
||||
// 1년 미만: 월 1일
|
||||
if (monthsDiff < 12) {
|
||||
return Math.floor(monthsDiff);
|
||||
}
|
||||
|
||||
// 1년 이상: 15일 기본 + 2년마다 1일 추가 (최대 25일)
|
||||
const yearsWorked = Math.floor(monthsDiff / 12);
|
||||
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) {
|
||||
const db = await getDb();
|
||||
|
||||
// 우선순위순으로 잔여 일수가 있는 잔액 조회
|
||||
const [balances] = await db.query(`
|
||||
SELECT vbd.id, vbd.vacation_type_id, vbd.total_days, vbd.used_days,
|
||||
(vbd.total_days - vbd.used_days) as remaining_days,
|
||||
@@ -284,7 +217,6 @@ const vacationBalanceModel = {
|
||||
`, [workerId, year]);
|
||||
|
||||
if (balances.length === 0) {
|
||||
// 잔액이 없어도 일단 기록은 저장 (경고만)
|
||||
console.warn(`[VacationBalance] 작업자 ${workerId}의 ${year}년 휴가 잔액이 없습니다`);
|
||||
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) {
|
||||
const db = await getDb();
|
||||
|
||||
// 우선순위 역순으로 사용 일수가 있는 잔액 조회 (나중에 차감된 것부터 복구)
|
||||
const [balances] = await db.query(`
|
||||
SELECT vbd.id, vbd.vacation_type_id, vbd.used_days,
|
||||
vt.type_code, vt.type_name, vt.priority
|
||||
@@ -375,25 +302,20 @@ const vacationBalanceModel = {
|
||||
/**
|
||||
* 특정 ID로 휴가 잔액 조회
|
||||
*/
|
||||
async getById(id, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
vbd.*,
|
||||
w.worker_name,
|
||||
vt.type_name,
|
||||
vt.type_code
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN workers w ON vbd.worker_id = w.worker_id
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.id = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [id]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async getById(id) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
vbd.*,
|
||||
w.worker_name,
|
||||
vt.type_name,
|
||||
vt.type_code
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN workers w ON vbd.worker_id = w.worker_id
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.id = ?
|
||||
`, [id]);
|
||||
return rows;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,123 +9,97 @@ const vacationTypeModel = {
|
||||
/**
|
||||
* 모든 활성 휴가 유형 조회 (우선순위 순서대로)
|
||||
*/
|
||||
async getAll(callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT *
|
||||
FROM vacation_types
|
||||
WHERE is_active = 1
|
||||
ORDER BY priority ASC, id ASC
|
||||
`;
|
||||
const [rows] = await db.query(query);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async getAll() {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT *
|
||||
FROM vacation_types
|
||||
WHERE is_active = 1
|
||||
ORDER BY priority ASC, id ASC
|
||||
`);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 시스템 기본 휴가 유형만 조회
|
||||
*/
|
||||
async getSystemTypes(callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT *
|
||||
FROM vacation_types
|
||||
WHERE is_system = 1 AND is_active = 1
|
||||
ORDER BY priority ASC
|
||||
`;
|
||||
const [rows] = await db.query(query);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async getSystemTypes() {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT *
|
||||
FROM vacation_types
|
||||
WHERE is_system = 1 AND is_active = 1
|
||||
ORDER BY priority ASC
|
||||
`);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 특별 휴가 유형만 조회
|
||||
*/
|
||||
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로 휴가 유형 조회
|
||||
*/
|
||||
async getById(id, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `SELECT * FROM vacation_types WHERE id = ?`;
|
||||
const [rows] = await db.query(query, [id]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async getById(id) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`SELECT * FROM vacation_types WHERE id = ?`, [id]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 유형 코드로 조회
|
||||
*/
|
||||
async getByCode(code, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `SELECT * FROM vacation_types WHERE type_code = ?`;
|
||||
const [rows] = await db.query(query, [code]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async getByCode(code) {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`SELECT * FROM vacation_types WHERE type_code = ?`, [code]);
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 유형 생성
|
||||
*/
|
||||
async create(typeData, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `INSERT INTO vacation_types SET ?`;
|
||||
const [result] = await db.query(query, typeData);
|
||||
callback(null, result);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async create(typeData) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`INSERT INTO vacation_types SET ?`, typeData);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 유형 수정
|
||||
*/
|
||||
async update(id, updateData, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `UPDATE vacation_types SET ? WHERE id = ?`;
|
||||
const [result] = await db.query(query, [updateData, id]);
|
||||
callback(null, result);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async update(id, updateData) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`UPDATE vacation_types SET ? WHERE id = ?`, [updateData, id]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 유형 삭제 (논리적 삭제 - is_active = 0)
|
||||
*/
|
||||
async delete(id, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `UPDATE vacation_types SET is_active = 0, updated_at = NOW() WHERE id = ?`;
|
||||
const [result] = await db.query(query, [id]);
|
||||
callback(null, result);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async delete(id) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`UPDATE vacation_types SET is_active = 0, updated_at = NOW() WHERE id = ?`, [id]);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 우선순위 업데이트
|
||||
*/
|
||||
async updatePriority(id, priority, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `UPDATE vacation_types SET priority = ?, updated_at = NOW() WHERE id = ?`;
|
||||
const [result] = await db.query(query, [priority, id]);
|
||||
callback(null, result);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
async updatePriority(id, priority) {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`UPDATE vacation_types SET priority = ?, updated_at = NOW() WHERE id = ?`, [priority, id]);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,484 +2,324 @@ const { getDb } = require('../dbPool');
|
||||
|
||||
// ==================== 출입 신청 관리 ====================
|
||||
|
||||
/**
|
||||
* 출입 신청 생성
|
||||
*/
|
||||
const createVisitRequest = async (requestData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
requester_id,
|
||||
visitor_company,
|
||||
visitor_count = 1,
|
||||
category_id,
|
||||
workplace_id,
|
||||
visit_date,
|
||||
visit_time,
|
||||
purpose_id,
|
||||
notes = null
|
||||
} = requestData;
|
||||
const createVisitRequest = async (requestData) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
requester_id,
|
||||
visitor_company,
|
||||
visitor_count = 1,
|
||||
category_id,
|
||||
workplace_id,
|
||||
visit_date,
|
||||
visit_time,
|
||||
purpose_id,
|
||||
notes = null
|
||||
} = requestData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workplace_visit_requests
|
||||
(requester_id, visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[requester_id, visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes]
|
||||
);
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workplace_visit_requests
|
||||
(requester_id, visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[requester_id, visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 목록 조회 (필터 옵션 포함)
|
||||
*/
|
||||
const getAllVisitRequests = async (filters = {}, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count,
|
||||
vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time,
|
||||
vr.purpose_id, vr.notes, vr.status,
|
||||
vr.approved_by, vr.approved_at, vr.rejection_reason,
|
||||
vr.created_at, vr.updated_at,
|
||||
u.username as requester_name, u.name as requester_full_name,
|
||||
wc.category_name, w.workplace_name,
|
||||
vpt.purpose_name,
|
||||
approver.username as approver_name
|
||||
FROM workplace_visit_requests vr
|
||||
INNER JOIN users u ON vr.requester_id = u.user_id
|
||||
INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id
|
||||
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 getAllVisitRequests = async (filters = {}) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count,
|
||||
vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time,
|
||||
vr.purpose_id, vr.notes, vr.status,
|
||||
vr.approved_by, vr.approved_at, vr.rejection_reason,
|
||||
vr.created_at, vr.updated_at,
|
||||
u.username as requester_name, u.name as requester_full_name,
|
||||
wc.category_name, w.workplace_name,
|
||||
vpt.purpose_name,
|
||||
approver.username as approver_name
|
||||
FROM workplace_visit_requests vr
|
||||
INNER JOIN users u ON vr.requester_id = u.user_id
|
||||
INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id
|
||||
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) {
|
||||
query += ` AND vr.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.status) {
|
||||
query += ` AND vr.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);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 상세 조회
|
||||
*/
|
||||
const getVisitRequestById = async (requestId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count,
|
||||
vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time,
|
||||
vr.purpose_id, vr.notes, vr.status,
|
||||
vr.approved_by, vr.approved_at, vr.rejection_reason,
|
||||
vr.created_at, vr.updated_at,
|
||||
u.username as requester_name, u.name as requester_full_name,
|
||||
wc.category_name, w.workplace_name,
|
||||
vpt.purpose_name,
|
||||
approver.username as approver_name
|
||||
FROM workplace_visit_requests vr
|
||||
INNER JOIN users u ON vr.requester_id = u.user_id
|
||||
INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id
|
||||
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 vr.request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getVisitRequestById = async (requestId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
vr.request_id, vr.requester_id, vr.visitor_company, vr.visitor_count,
|
||||
vr.category_id, vr.workplace_id, vr.visit_date, vr.visit_time,
|
||||
vr.purpose_id, vr.notes, vr.status,
|
||||
vr.approved_by, vr.approved_at, vr.rejection_reason,
|
||||
vr.created_at, vr.updated_at,
|
||||
u.username as requester_name, u.name as requester_full_name,
|
||||
wc.category_name, w.workplace_name,
|
||||
vpt.purpose_name,
|
||||
approver.username as approver_name
|
||||
FROM workplace_visit_requests vr
|
||||
INNER JOIN users u ON vr.requester_id = u.user_id
|
||||
INNER JOIN workplace_categories wc ON vr.category_id = wc.category_id
|
||||
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 vr.request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 수정
|
||||
*/
|
||||
const updateVisitRequest = async (requestId, requestData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
visitor_company,
|
||||
visitor_count,
|
||||
category_id,
|
||||
workplace_id,
|
||||
visit_date,
|
||||
visit_time,
|
||||
purpose_id,
|
||||
notes
|
||||
} = requestData;
|
||||
const updateVisitRequest = async (requestId, requestData) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes
|
||||
} = requestData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET visitor_company = ?, visitor_count = ?, category_id = ?, workplace_id = ?,
|
||||
visit_date = ?, visit_time = ?, purpose_id = ?, notes = ?, updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes, requestId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET visitor_company = ?, visitor_count = ?, category_id = ?, workplace_id = ?,
|
||||
visit_date = ?, visit_time = ?, purpose_id = ?, notes = ?, updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[visitor_company, visitor_count, category_id, workplace_id,
|
||||
visit_date, visit_time, purpose_id, notes, requestId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 삭제
|
||||
*/
|
||||
const deleteVisitRequest = async (requestId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_visit_requests WHERE request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const deleteVisitRequest = async (requestId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_visit_requests WHERE request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 승인
|
||||
*/
|
||||
const approveVisitRequest = async (requestId, approvedBy, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET status = 'approved', approved_by = ?, approved_at = NOW(), updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[approvedBy, requestId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const approveVisitRequest = async (requestId, approvedBy) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET status = 'approved', approved_by = ?, approved_at = NOW(), updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[approvedBy, requestId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 반려
|
||||
*/
|
||||
const rejectVisitRequest = async (requestId, rejectionData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const { approved_by, rejection_reason } = rejectionData;
|
||||
const rejectVisitRequest = async (requestId, rejectionData) => {
|
||||
const db = await getDb();
|
||||
const { approved_by, rejection_reason } = rejectionData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET status = 'rejected', approved_by = ?, approved_at = NOW(),
|
||||
rejection_reason = ?, updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[approved_by, rejection_reason, requestId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET status = 'rejected', approved_by = ?, approved_at = NOW(),
|
||||
rejection_reason = ?, updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[approved_by, rejection_reason, requestId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 출입 신청 상태 변경
|
||||
*/
|
||||
const updateVisitRequestStatus = async (requestId, status, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET status = ?, updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[status, requestId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const updateVisitRequestStatus = async (requestId, status) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_visit_requests
|
||||
SET status = ?, updated_at = NOW()
|
||||
WHERE request_id = ?`,
|
||||
[status, requestId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 방문 목적 관리 ====================
|
||||
|
||||
/**
|
||||
* 모든 방문 목적 조회
|
||||
*/
|
||||
const getAllVisitPurposes = async (callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT purpose_id, purpose_name, display_order, is_active, created_at
|
||||
FROM visit_purpose_types
|
||||
ORDER BY display_order ASC, purpose_id ASC`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getAllVisitPurposes = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT purpose_id, purpose_name, display_order, is_active, created_at
|
||||
FROM visit_purpose_types
|
||||
ORDER BY display_order ASC, purpose_id ASC`
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 활성 방문 목적만 조회
|
||||
*/
|
||||
const getActiveVisitPurposes = async (callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT purpose_id, purpose_name, display_order, is_active, created_at
|
||||
FROM visit_purpose_types
|
||||
WHERE is_active = TRUE
|
||||
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(
|
||||
`SELECT purpose_id, purpose_name, display_order, is_active, created_at
|
||||
FROM visit_purpose_types
|
||||
WHERE is_active = TRUE
|
||||
ORDER BY display_order ASC, purpose_id ASC`
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 방문 목적 추가
|
||||
*/
|
||||
const createVisitPurpose = async (purposeData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const { purpose_name, display_order = 0, is_active = true } = purposeData;
|
||||
const createVisitPurpose = async (purposeData) => {
|
||||
const db = await getDb();
|
||||
const { purpose_name, display_order = 0, is_active = true } = purposeData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO visit_purpose_types (purpose_name, display_order, is_active)
|
||||
VALUES (?, ?, ?)`,
|
||||
[purpose_name, display_order, is_active]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO visit_purpose_types (purpose_name, display_order, is_active)
|
||||
VALUES (?, ?, ?)`,
|
||||
[purpose_name, display_order, is_active]
|
||||
);
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 방문 목적 수정
|
||||
*/
|
||||
const updateVisitPurpose = async (purposeId, purposeData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const { purpose_name, display_order, is_active } = purposeData;
|
||||
const updateVisitPurpose = async (purposeId, purposeData) => {
|
||||
const db = await getDb();
|
||||
const { purpose_name, display_order, is_active } = purposeData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE visit_purpose_types
|
||||
SET purpose_name = ?, display_order = ?, is_active = ?
|
||||
WHERE purpose_id = ?`,
|
||||
[purpose_name, display_order, is_active, purposeId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const [result] = await db.query(
|
||||
`UPDATE visit_purpose_types
|
||||
SET purpose_name = ?, display_order = ?, is_active = ?
|
||||
WHERE purpose_id = ?`,
|
||||
[purpose_name, display_order, is_active, purposeId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 방문 목적 삭제
|
||||
*/
|
||||
const deleteVisitPurpose = async (purposeId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM visit_purpose_types WHERE purpose_id = ?`,
|
||||
[purposeId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const deleteVisitPurpose = async (purposeId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM visit_purpose_types WHERE purpose_id = ?`,
|
||||
[purposeId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 안전교육 기록 관리 ====================
|
||||
|
||||
/**
|
||||
* 안전교육 기록 생성
|
||||
*/
|
||||
const createTrainingRecord = async (trainingData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
request_id,
|
||||
trainer_id,
|
||||
training_date,
|
||||
training_start_time,
|
||||
training_end_time = null,
|
||||
training_topics = null
|
||||
} = trainingData;
|
||||
const createTrainingRecord = async (trainingData) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
request_id, trainer_id, training_date,
|
||||
training_start_time, training_end_time = null, training_topics = null
|
||||
} = trainingData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO safety_training_records
|
||||
(request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO safety_training_records
|
||||
(request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[request_id, trainer_id, training_date, training_start_time, training_end_time, training_topics]
|
||||
);
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 특정 출입 신청의 안전교육 기록 조회
|
||||
*/
|
||||
const getTrainingRecordByRequestId = async (requestId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
str.training_id, str.request_id, str.trainer_id, str.training_date,
|
||||
str.training_start_time, str.training_end_time, str.training_topics,
|
||||
str.signature_data, str.completed_at, str.created_at, str.updated_at,
|
||||
u.username as trainer_name, u.name as trainer_full_name
|
||||
FROM safety_training_records str
|
||||
INNER JOIN users u ON str.trainer_id = u.user_id
|
||||
WHERE str.request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getTrainingRecordByRequestId = async (requestId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
str.training_id, str.request_id, str.trainer_id, str.training_date,
|
||||
str.training_start_time, str.training_end_time, str.training_topics,
|
||||
str.signature_data, str.completed_at, str.created_at, str.updated_at,
|
||||
u.username as trainer_name, u.name as trainer_full_name
|
||||
FROM safety_training_records str
|
||||
INNER JOIN users u ON str.trainer_id = u.user_id
|
||||
WHERE str.request_id = ?`,
|
||||
[requestId]
|
||||
);
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 안전교육 기록 수정
|
||||
*/
|
||||
const updateTrainingRecord = async (trainingId, trainingData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
training_date,
|
||||
training_start_time,
|
||||
training_end_time,
|
||||
training_topics
|
||||
} = trainingData;
|
||||
const updateTrainingRecord = async (trainingId, trainingData) => {
|
||||
const db = await getDb();
|
||||
const { training_date, training_start_time, training_end_time, training_topics } = trainingData;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE safety_training_records
|
||||
SET training_date = ?, training_start_time = ?, training_end_time = ?,
|
||||
training_topics = ?, updated_at = NOW()
|
||||
WHERE training_id = ?`,
|
||||
[training_date, training_start_time, training_end_time, training_topics, trainingId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const [result] = await db.query(
|
||||
`UPDATE safety_training_records
|
||||
SET training_date = ?, training_start_time = ?, training_end_time = ?,
|
||||
training_topics = ?, updated_at = NOW()
|
||||
WHERE training_id = ?`,
|
||||
[training_date, training_start_time, training_end_time, training_topics, trainingId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 안전교육 완료 (서명 포함)
|
||||
*/
|
||||
const completeTraining = async (trainingId, signatureData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE safety_training_records
|
||||
SET signature_data = ?, completed_at = NOW(), updated_at = NOW()
|
||||
WHERE training_id = ?`,
|
||||
[signatureData, trainingId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const completeTraining = async (trainingId, signatureData) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`UPDATE safety_training_records
|
||||
SET signature_data = ?, completed_at = NOW(), updated_at = NOW()
|
||||
WHERE training_id = ?`,
|
||||
[signatureData, trainingId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 안전교육 목록 조회 (날짜별 필터)
|
||||
*/
|
||||
const getTrainingRecords = async (filters = {}, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
str.training_id, str.request_id, str.trainer_id, str.training_date,
|
||||
str.training_start_time, str.training_end_time, str.training_topics,
|
||||
str.completed_at, str.created_at, str.updated_at,
|
||||
u.username as trainer_name, u.name as trainer_full_name,
|
||||
vr.visitor_company, vr.visitor_count, vr.visit_date
|
||||
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 getTrainingRecords = async (filters = {}) => {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
str.training_id, str.request_id, str.trainer_id, str.training_date,
|
||||
str.training_start_time, str.training_end_time, str.training_topics,
|
||||
str.completed_at, str.created_at, str.updated_at,
|
||||
u.username as trainer_name, u.name as trainer_full_name,
|
||||
vr.visitor_company, vr.visitor_count, vr.visit_date
|
||||
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) {
|
||||
query += ` AND str.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.training_date) {
|
||||
query += ` AND str.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);
|
||||
return rows;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// 출입 신청
|
||||
createVisitRequest,
|
||||
getAllVisitRequests,
|
||||
getVisitRequestById,
|
||||
@@ -488,15 +328,11 @@ module.exports = {
|
||||
approveVisitRequest,
|
||||
rejectVisitRequest,
|
||||
updateVisitRequestStatus,
|
||||
|
||||
// 방문 목적
|
||||
getAllVisitPurposes,
|
||||
getActiveVisitPurposes,
|
||||
createVisitPurpose,
|
||||
updateVisitPurpose,
|
||||
deleteVisitPurpose,
|
||||
|
||||
// 안전교육
|
||||
createTrainingRecord,
|
||||
getTrainingRecordByRequestId,
|
||||
updateTrainingRecord,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ const { getDb } = require('../dbPool');
|
||||
/**
|
||||
* 1. 여러 건 등록 (트랜잭션 사용)
|
||||
*/
|
||||
const createBatch = async (reports, callback) => {
|
||||
const createBatch = async (reports) => {
|
||||
const db = await getDb();
|
||||
const conn = await db.getConnection();
|
||||
|
||||
@@ -30,10 +30,9 @@ const createBatch = async (reports, callback) => {
|
||||
}
|
||||
|
||||
await conn.commit();
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
callback(err);
|
||||
throw err;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
@@ -42,175 +41,146 @@ const createBatch = async (reports, callback) => {
|
||||
/**
|
||||
* 2. 단일 등록
|
||||
*/
|
||||
const create = async (report, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
date, worker_id, project_id,
|
||||
task_id, overtime_hours,
|
||||
work_details, memo
|
||||
} = report;
|
||||
const create = async (report) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
date, worker_id, project_id,
|
||||
task_id, overtime_hours,
|
||||
work_details, memo
|
||||
} = report;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO WorkReports
|
||||
(\`date\`, worker_id, project_id, task_id, overtime_hours, work_details, memo)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
date,
|
||||
worker_id,
|
||||
project_id,
|
||||
task_id || null,
|
||||
overtime_hours || null,
|
||||
work_details || null,
|
||||
memo || null
|
||||
]
|
||||
);
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO WorkReports
|
||||
(\`date\`, worker_id, project_id, task_id, overtime_hours, work_details, memo)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
date,
|
||||
worker_id,
|
||||
project_id,
|
||||
task_id || null,
|
||||
overtime_hours || null,
|
||||
work_details || null,
|
||||
memo || null
|
||||
]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 3. 날짜별 조회
|
||||
*/
|
||||
const getAllByDate = async (date, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
SELECT
|
||||
wr.worker_id, -- 이 줄을 추가했습니다
|
||||
wr.id,
|
||||
wr.\`date\`,
|
||||
w.worker_name,
|
||||
p.project_name,
|
||||
CONCAT(t.category, ':', t.subcategory) AS task_name,
|
||||
wr.overtime_hours,
|
||||
wr.work_details,
|
||||
wr.memo
|
||||
FROM WorkReports wr
|
||||
LEFT JOIN workers w ON wr.worker_id = w.worker_id
|
||||
LEFT JOIN projects p ON wr.project_id = p.project_id
|
||||
LEFT JOIN Tasks t ON wr.task_id = t.task_id
|
||||
WHERE wr.\`date\` = ?
|
||||
ORDER BY w.worker_name ASC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [date]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 3. 날짜별 조회
|
||||
*/
|
||||
const getAllByDate = async (date) => {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
SELECT
|
||||
wr.worker_id,
|
||||
wr.id,
|
||||
wr.\`date\`,
|
||||
w.worker_name,
|
||||
p.project_name,
|
||||
CONCAT(t.category, ':', t.subcategory) AS task_name,
|
||||
wr.overtime_hours,
|
||||
wr.work_details,
|
||||
wr.memo
|
||||
FROM WorkReports wr
|
||||
LEFT JOIN workers w ON wr.worker_id = w.worker_id
|
||||
LEFT JOIN projects p ON wr.project_id = p.project_id
|
||||
LEFT JOIN Tasks t ON wr.task_id = t.task_id
|
||||
WHERE wr.\`date\` = ?
|
||||
ORDER BY w.worker_name ASC
|
||||
`;
|
||||
const [rows] = await db.query(sql, [date]);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 4. 기간 조회
|
||||
*/
|
||||
const getByRange = async (start, end, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
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 \`date\` BETWEEN ? AND ?
|
||||
ORDER BY \`date\` ASC`,
|
||||
[start, end]
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getByRange = async (start, end) => {
|
||||
const db = await getDb();
|
||||
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 \`date\` BETWEEN ? AND ?
|
||||
ORDER BY \`date\` ASC`,
|
||||
[start, end]
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 5. ID로 조회
|
||||
*/
|
||||
const getById = async (id, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
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 = ?`,
|
||||
[id]
|
||||
);
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getById = async (id) => {
|
||||
const db = await getDb();
|
||||
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 = ?`,
|
||||
[id]
|
||||
);
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 6. 수정
|
||||
*/
|
||||
const update = async (id, report, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
date, worker_id, project_id,
|
||||
task_id, overtime_hours,
|
||||
work_details, memo
|
||||
} = report;
|
||||
const update = async (id, report) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
date, worker_id, project_id,
|
||||
task_id, overtime_hours,
|
||||
work_details, memo
|
||||
} = report;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE WorkReports
|
||||
SET \`date\` = ?,
|
||||
worker_id = ?,
|
||||
project_id = ?,
|
||||
task_id = ?,
|
||||
overtime_hours = ?,
|
||||
work_details = ?,
|
||||
memo = ?,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?`,
|
||||
[
|
||||
date,
|
||||
worker_id,
|
||||
project_id,
|
||||
task_id || null,
|
||||
overtime_hours || null,
|
||||
work_details || null,
|
||||
memo || null,
|
||||
id
|
||||
]
|
||||
);
|
||||
const [result] = await db.query(
|
||||
`UPDATE WorkReports
|
||||
SET \`date\` = ?,
|
||||
worker_id = ?,
|
||||
project_id = ?,
|
||||
task_id = ?,
|
||||
overtime_hours = ?,
|
||||
work_details = ?,
|
||||
memo = ?,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?`,
|
||||
[
|
||||
date,
|
||||
worker_id,
|
||||
project_id,
|
||||
task_id || null,
|
||||
overtime_hours || null,
|
||||
work_details || null,
|
||||
memo || null,
|
||||
id
|
||||
]
|
||||
);
|
||||
|
||||
callback(null, result.affectedRows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.affectedRows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 7. 삭제
|
||||
*/
|
||||
const remove = async (id, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM WorkReports WHERE id = ?`,
|
||||
[id]
|
||||
);
|
||||
callback(null, result.affectedRows);
|
||||
} catch (err) {
|
||||
callback(new Error(err.message || String(err)));
|
||||
}
|
||||
const remove = async (id) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM WorkReports WHERE id = ?`,
|
||||
[id]
|
||||
);
|
||||
return result.affectedRows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 8. 중복 확인
|
||||
*/
|
||||
const existsByDateAndWorker = async (date, worker_id, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT 1 FROM WorkReports WHERE \`date\` = ? AND worker_id = ? LIMIT 1`,
|
||||
[date, worker_id]
|
||||
);
|
||||
callback(null, rows.length > 0);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const existsByDateAndWorker = async (date, worker_id) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT 1 FROM WorkReports WHERE \`date\` = ? AND worker_id = ? LIMIT 1`,
|
||||
[date, worker_id]
|
||||
);
|
||||
return rows.length > 0;
|
||||
};
|
||||
|
||||
// ✅ 내보내기
|
||||
module.exports = {
|
||||
create,
|
||||
createBatch,
|
||||
@@ -220,4 +190,4 @@ module.exports = {
|
||||
update,
|
||||
remove,
|
||||
existsByDateAndWorker
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,426 +2,290 @@ const { getDb } = require('../dbPool');
|
||||
|
||||
// ==================== 카테고리(공장) 관련 ====================
|
||||
|
||||
/**
|
||||
* 카테고리 생성
|
||||
*/
|
||||
const createCategory = async (category, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
category_name,
|
||||
description = null,
|
||||
display_order = 0,
|
||||
is_active = true
|
||||
} = category;
|
||||
const createCategory = async (category) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
category_name,
|
||||
description = null,
|
||||
display_order = 0,
|
||||
is_active = true
|
||||
} = category;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workplace_categories
|
||||
(category_name, description, display_order, is_active)
|
||||
VALUES (?, ?, ?, ?)`,
|
||||
[category_name, description, display_order, is_active]
|
||||
);
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workplace_categories
|
||||
(category_name, description, display_order, is_active)
|
||||
VALUES (?, ?, ?, ?)`,
|
||||
[category_name, description, display_order, is_active]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 모든 카테고리 조회
|
||||
*/
|
||||
const getAllCategories = async (callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
|
||||
FROM workplace_categories
|
||||
ORDER BY display_order ASC, category_id ASC`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getAllCategories = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
|
||||
FROM workplace_categories
|
||||
ORDER BY display_order ASC, category_id ASC`
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 활성 카테고리만 조회
|
||||
*/
|
||||
const getActiveCategories = async (callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
|
||||
FROM workplace_categories
|
||||
WHERE is_active = TRUE
|
||||
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(
|
||||
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
|
||||
FROM workplace_categories
|
||||
WHERE is_active = TRUE
|
||||
ORDER BY display_order ASC, category_id ASC`
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* ID로 카테고리 조회
|
||||
*/
|
||||
const getCategoryById = async (categoryId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
|
||||
FROM workplace_categories
|
||||
WHERE category_id = ?`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getCategoryById = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT category_id, category_name, description, display_order, is_active, layout_image, created_at, updated_at
|
||||
FROM workplace_categories
|
||||
WHERE category_id = ?`,
|
||||
[categoryId]
|
||||
);
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리 수정
|
||||
*/
|
||||
const updateCategory = async (categoryId, category, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
category_name,
|
||||
description,
|
||||
display_order,
|
||||
is_active,
|
||||
layout_image
|
||||
} = category;
|
||||
const updateCategory = async (categoryId, category) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
category_name,
|
||||
description,
|
||||
display_order,
|
||||
is_active,
|
||||
layout_image
|
||||
} = category;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_categories
|
||||
SET category_name = ?, description = ?, display_order = ?, is_active = ?, layout_image = ?, updated_at = NOW()
|
||||
WHERE category_id = ?`,
|
||||
[category_name, description, display_order, is_active, layout_image, categoryId]
|
||||
);
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_categories
|
||||
SET category_name = ?, description = ?, display_order = ?, is_active = ?, layout_image = ?, updated_at = NOW()
|
||||
WHERE category_id = ?`,
|
||||
[category_name, description, display_order, is_active, layout_image, categoryId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리 삭제
|
||||
*/
|
||||
const deleteCategory = async (categoryId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_categories WHERE category_id = ?`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const deleteCategory = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_categories WHERE category_id = ?`,
|
||||
[categoryId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 작업장 관련 ====================
|
||||
|
||||
/**
|
||||
* 작업장 생성
|
||||
*/
|
||||
const createWorkplace = async (workplace, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
category_id = null,
|
||||
workplace_name,
|
||||
description = null,
|
||||
is_active = true,
|
||||
workplace_purpose = null,
|
||||
display_priority = 0
|
||||
} = workplace;
|
||||
const createWorkplace = async (workplace) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
category_id = null,
|
||||
workplace_name,
|
||||
description = null,
|
||||
is_active = true,
|
||||
workplace_purpose = null,
|
||||
display_priority = 0
|
||||
} = workplace;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workplaces
|
||||
(category_id, workplace_name, description, is_active, workplace_purpose, display_priority)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[category_id, workplace_name, description, is_active, workplace_purpose, display_priority]
|
||||
);
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workplaces
|
||||
(category_id, workplace_name, description, is_active, workplace_purpose, display_priority)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[category_id, workplace_name, description, is_active, workplace_purpose, display_priority]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 모든 작업장 조회 (카테고리 정보 포함)
|
||||
*/
|
||||
const getAllWorkplaces = async (callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
ORDER BY wc.display_order ASC, w.display_priority ASC, w.workplace_id DESC`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getAllWorkplaces = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
ORDER BY wc.display_order ASC, w.display_priority ASC, w.workplace_id DESC`
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 활성 작업장만 조회
|
||||
*/
|
||||
const getActiveWorkplaces = async (callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE w.is_active = TRUE
|
||||
ORDER BY wc.display_order ASC, w.workplace_id DESC`
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getActiveWorkplaces = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE w.is_active = TRUE
|
||||
ORDER BY wc.display_order ASC, w.workplace_id DESC`
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리별 작업장 조회
|
||||
*/
|
||||
const getWorkplacesByCategory = async (categoryId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE w.category_id = ?
|
||||
ORDER BY w.workplace_id DESC`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getWorkplacesByCategory = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE w.category_id = ?
|
||||
ORDER BY w.workplace_id DESC`,
|
||||
[categoryId]
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* ID로 작업장 조회
|
||||
*/
|
||||
const getWorkplaceById = async (workplaceId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE w.workplace_id = ?`,
|
||||
[workplaceId]
|
||||
);
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getWorkplaceById = async (workplaceId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, w.workplace_purpose, w.display_priority,
|
||||
w.layout_image, w.created_at, w.updated_at,
|
||||
wc.category_name
|
||||
FROM workplaces w
|
||||
LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id
|
||||
WHERE w.workplace_id = ?`,
|
||||
[workplaceId]
|
||||
);
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업장 수정
|
||||
*/
|
||||
const updateWorkplace = async (workplaceId, workplace, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
category_id,
|
||||
workplace_name,
|
||||
description,
|
||||
is_active,
|
||||
workplace_purpose,
|
||||
display_priority,
|
||||
layout_image
|
||||
} = workplace;
|
||||
const updateWorkplace = async (workplaceId, workplace) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
category_id,
|
||||
workplace_name,
|
||||
description,
|
||||
is_active,
|
||||
workplace_purpose,
|
||||
display_priority,
|
||||
layout_image
|
||||
} = workplace;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplaces
|
||||
SET category_id = ?, workplace_name = ?, description = ?, is_active = ?,
|
||||
workplace_purpose = ?, display_priority = ?, layout_image = ?, updated_at = NOW()
|
||||
WHERE workplace_id = ?`,
|
||||
[category_id, workplace_name, description, is_active, workplace_purpose, display_priority, layout_image, workplaceId]
|
||||
);
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplaces
|
||||
SET category_id = ?, workplace_name = ?, description = ?, is_active = ?,
|
||||
workplace_purpose = ?, display_priority = ?, layout_image = ?, updated_at = NOW()
|
||||
WHERE workplace_id = ?`,
|
||||
[category_id, workplace_name, description, is_active, workplace_purpose, display_priority, layout_image, workplaceId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업장 삭제
|
||||
*/
|
||||
const deleteWorkplace = async (workplaceId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplaces WHERE workplace_id = ?`,
|
||||
[workplaceId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const deleteWorkplace = async (workplaceId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplaces WHERE workplace_id = ?`,
|
||||
[workplaceId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
// ==================== 작업장 지도 영역 관련 ====================
|
||||
|
||||
/**
|
||||
* 작업장 지도 영역 생성
|
||||
*/
|
||||
const createMapRegion = async (region, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
workplace_id,
|
||||
category_id,
|
||||
x_start,
|
||||
y_start,
|
||||
x_end,
|
||||
y_end,
|
||||
shape = 'rect',
|
||||
polygon_points = null
|
||||
} = region;
|
||||
const createMapRegion = async (region) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
workplace_id,
|
||||
category_id,
|
||||
x_start,
|
||||
y_start,
|
||||
x_end,
|
||||
y_end,
|
||||
shape = 'rect',
|
||||
polygon_points = null
|
||||
} = region;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workplace_map_regions
|
||||
(workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points]
|
||||
);
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workplace_map_regions
|
||||
(workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[workplace_id, category_id, x_start, y_start, x_end, y_end, shape, polygon_points]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
/**
|
||||
* 카테고리(공장)별 지도 영역 조회
|
||||
*/
|
||||
const getMapRegionsByCategory = async (categoryId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT mr.*, w.workplace_name, w.description
|
||||
FROM workplace_map_regions mr
|
||||
INNER JOIN workplaces w ON mr.workplace_id = w.workplace_id
|
||||
WHERE mr.category_id = ? AND w.is_active = TRUE
|
||||
ORDER BY mr.region_id ASC`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getMapRegionsByCategory = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT mr.*, w.workplace_name, w.description
|
||||
FROM workplace_map_regions mr
|
||||
INNER JOIN workplaces w ON mr.workplace_id = w.workplace_id
|
||||
WHERE mr.category_id = ? AND w.is_active = TRUE
|
||||
ORDER BY mr.region_id ASC`,
|
||||
[categoryId]
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업장별 지도 영역 조회
|
||||
*/
|
||||
const getMapRegionByWorkplace = async (workplaceId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT * FROM workplace_map_regions WHERE workplace_id = ?`,
|
||||
[workplaceId]
|
||||
);
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getMapRegionByWorkplace = async (workplaceId) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT * FROM workplace_map_regions WHERE workplace_id = ?`,
|
||||
[workplaceId]
|
||||
);
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 지도 영역 수정
|
||||
*/
|
||||
const updateMapRegion = async (regionId, region, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
x_start,
|
||||
y_start,
|
||||
x_end,
|
||||
y_end,
|
||||
shape,
|
||||
polygon_points
|
||||
} = region;
|
||||
const updateMapRegion = async (regionId, region) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
x_start,
|
||||
y_start,
|
||||
x_end,
|
||||
y_end,
|
||||
shape,
|
||||
polygon_points
|
||||
} = region;
|
||||
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_map_regions
|
||||
SET x_start = ?, y_start = ?, x_end = ?, y_end = ?, shape = ?, polygon_points = ?, updated_at = NOW()
|
||||
WHERE region_id = ?`,
|
||||
[x_start, y_start, x_end, y_end, shape, polygon_points, regionId]
|
||||
);
|
||||
const [result] = await db.query(
|
||||
`UPDATE workplace_map_regions
|
||||
SET x_start = ?, y_start = ?, x_end = ?, y_end = ?, shape = ?, polygon_points = ?, updated_at = NOW()
|
||||
WHERE region_id = ?`,
|
||||
[x_start, y_start, x_end, y_end, shape, polygon_points, regionId]
|
||||
);
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 지도 영역 삭제
|
||||
*/
|
||||
const deleteMapRegion = async (regionId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_map_regions WHERE region_id = ?`,
|
||||
[regionId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const deleteMapRegion = async (regionId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_map_regions WHERE region_id = ?`,
|
||||
[regionId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 작업장 영역 일괄 삭제 (카테고리별)
|
||||
*/
|
||||
const deleteMapRegionsByCategory = async (categoryId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_map_regions WHERE category_id = ?`,
|
||||
[categoryId]
|
||||
);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const deleteMapRegionsByCategory = async (categoryId) => {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(
|
||||
`DELETE FROM workplace_map_regions WHERE category_id = ?`,
|
||||
[categoryId]
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// 카테고리
|
||||
createCategory,
|
||||
getAllCategories,
|
||||
getActiveCategories,
|
||||
getCategoryById,
|
||||
updateCategory,
|
||||
deleteCategory,
|
||||
|
||||
// 작업장
|
||||
createWorkplace,
|
||||
getAllWorkplaces,
|
||||
getActiveWorkplaces,
|
||||
@@ -429,8 +293,6 @@ module.exports = {
|
||||
getWorkplaceById,
|
||||
updateWorkplace,
|
||||
deleteWorkplace,
|
||||
|
||||
// 지도 영역
|
||||
createMapRegion,
|
||||
getMapRegionsByCategory,
|
||||
getMapRegionByWorkplace,
|
||||
|
||||
@@ -24,16 +24,10 @@ const createWorkReportService = async (reportData) => {
|
||||
|
||||
logger.info('작업 보고서 생성 요청', { count: reports.length });
|
||||
|
||||
const workReport_ids = [];
|
||||
|
||||
try {
|
||||
const workReport_ids = [];
|
||||
for (const report of reports) {
|
||||
const id = await new Promise((resolve, reject) => {
|
||||
workReportModel.create(report, (err, insertId) => {
|
||||
if (err) reject(err);
|
||||
else resolve(insertId);
|
||||
});
|
||||
});
|
||||
const id = await workReportModel.create(report);
|
||||
workReport_ids.push(id);
|
||||
}
|
||||
|
||||
@@ -66,15 +60,8 @@ const getWorkReportsByDateService = async (date) => {
|
||||
logger.info('작업 보고서 날짜별 조회 요청', { date });
|
||||
|
||||
try {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workReportModel.getAllByDate(date, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const rows = await workReportModel.getAllByDate(date);
|
||||
logger.info('작업 보고서 조회 성공', { date, count: rows.length });
|
||||
|
||||
return rows;
|
||||
} catch (error) {
|
||||
logger.error('작업 보고서 조회 실패', { date, error: error.message });
|
||||
@@ -96,15 +83,8 @@ const getWorkReportsInRangeService = async (start, end) => {
|
||||
logger.info('작업 보고서 기간별 조회 요청', { start, end });
|
||||
|
||||
try {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workReportModel.getByRange(start, end, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const rows = await workReportModel.getByRange(start, end);
|
||||
logger.info('작업 보고서 조회 성공', { start, end, count: rows.length });
|
||||
|
||||
return rows;
|
||||
} catch (error) {
|
||||
logger.error('작업 보고서 조회 실패', { start, end, error: error.message });
|
||||
@@ -123,12 +103,7 @@ const getWorkReportByIdService = async (id) => {
|
||||
logger.info('작업 보고서 조회 요청', { report_id: id });
|
||||
|
||||
try {
|
||||
const row = await new Promise((resolve, reject) => {
|
||||
workReportModel.getById(id, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const row = await workReportModel.getById(id);
|
||||
|
||||
if (!row) {
|
||||
logger.warn('작업 보고서를 찾을 수 없음', { report_id: id });
|
||||
@@ -136,13 +111,9 @@ const getWorkReportByIdService = async (id) => {
|
||||
}
|
||||
|
||||
logger.info('작업 보고서 조회 성공', { report_id: id });
|
||||
|
||||
return row;
|
||||
} catch (error) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (error instanceof NotFoundError) throw error;
|
||||
logger.error('작업 보고서 조회 실패', { report_id: id, error: error.message });
|
||||
throw new DatabaseError('작업 보고서 조회 중 오류가 발생했습니다');
|
||||
}
|
||||
@@ -159,12 +130,7 @@ const updateWorkReportService = async (id, updateData) => {
|
||||
logger.info('작업 보고서 수정 요청', { report_id: id });
|
||||
|
||||
try {
|
||||
const changes = await new Promise((resolve, reject) => {
|
||||
workReportModel.update(id, updateData, (err, affectedRows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(affectedRows);
|
||||
});
|
||||
});
|
||||
const changes = await workReportModel.update(id, updateData);
|
||||
|
||||
if (changes === 0) {
|
||||
logger.warn('작업 보고서를 찾을 수 없거나 변경사항 없음', { report_id: id });
|
||||
@@ -172,13 +138,9 @@ const updateWorkReportService = async (id, updateData) => {
|
||||
}
|
||||
|
||||
logger.info('작업 보고서 수정 성공', { report_id: id, changes });
|
||||
|
||||
return { changes };
|
||||
} catch (error) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (error instanceof NotFoundError) throw error;
|
||||
logger.error('작업 보고서 수정 실패', { report_id: id, error: error.message });
|
||||
throw new DatabaseError('작업 보고서 수정 중 오류가 발생했습니다');
|
||||
}
|
||||
@@ -195,12 +157,7 @@ const removeWorkReportService = async (id) => {
|
||||
logger.info('작업 보고서 삭제 요청', { report_id: id });
|
||||
|
||||
try {
|
||||
const changes = await new Promise((resolve, reject) => {
|
||||
workReportModel.remove(id, (err, affectedRows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(affectedRows);
|
||||
});
|
||||
});
|
||||
const changes = await workReportModel.remove(id);
|
||||
|
||||
if (changes === 0) {
|
||||
logger.warn('작업 보고서를 찾을 수 없음', { report_id: id });
|
||||
@@ -208,13 +165,9 @@ const removeWorkReportService = async (id) => {
|
||||
}
|
||||
|
||||
logger.info('작업 보고서 삭제 성공', { report_id: id, changes });
|
||||
|
||||
return { changes };
|
||||
} catch (error) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (error instanceof NotFoundError) throw error;
|
||||
logger.error('작업 보고서 삭제 실패', { report_id: id, error: error.message });
|
||||
throw new DatabaseError('작업 보고서 삭제 중 오류가 발생했습니다');
|
||||
}
|
||||
@@ -237,35 +190,18 @@ const getSummaryService = async (year, month) => {
|
||||
logger.info('작업 보고서 월간 요약 조회 요청', { year, month, start, end });
|
||||
|
||||
try {
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
workReportModel.getByRange(start, end, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
const rows = await workReportModel.getByRange(start, end);
|
||||
|
||||
if (!rows || rows.length === 0) {
|
||||
logger.warn('월간 요약 데이터 없음', { year, month });
|
||||
throw new NotFoundError('해당 기간의 작업 보고서가 없습니다');
|
||||
}
|
||||
|
||||
logger.info('작업 보고서 월간 요약 조회 성공', {
|
||||
year,
|
||||
month,
|
||||
count: rows.length
|
||||
});
|
||||
|
||||
logger.info('작업 보고서 월간 요약 조회 성공', { year, month, count: rows.length });
|
||||
return rows;
|
||||
} catch (error) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.error('작업 보고서 월간 요약 조회 실패', {
|
||||
year,
|
||||
month,
|
||||
error: error.message
|
||||
});
|
||||
if (error instanceof NotFoundError) throw error;
|
||||
logger.error('작업 보고서 월간 요약 조회 실패', { year, month, error: error.message });
|
||||
throw new DatabaseError('월간 요약 조회 중 오류가 발생했습니다');
|
||||
}
|
||||
};
|
||||
@@ -274,7 +210,6 @@ const getSummaryService = async (year, month) => {
|
||||
|
||||
/**
|
||||
* 작업 보고서의 부적합 원인 목록 조회
|
||||
* - error_type_id 또는 issue_report_id 중 하나로 연결
|
||||
*/
|
||||
const getReportDefectsService = async (reportId) => {
|
||||
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 db = await getDb();
|
||||
try {
|
||||
await db.query('START TRANSACTION');
|
||||
|
||||
// 기존 부적합 원인 삭제
|
||||
await db.execute('DELETE FROM work_report_defects WHERE report_id = ?', [reportId]);
|
||||
|
||||
// 새 부적합 원인 추가
|
||||
if (defects && defects.length > 0) {
|
||||
for (const defect of defects) {
|
||||
// issue_report_id > category_id/item_id > error_type_id 순으로 우선
|
||||
await db.execute(`
|
||||
INSERT INTO work_report_defects (report_id, error_type_id, issue_report_id, category_id, item_id, defect_hours, note)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
@@ -344,12 +273,10 @@ const saveReportDefectsService = async (reportId, defects) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 총 부적합 시간 계산 및 daily_work_reports 업데이트
|
||||
const totalErrorHours = defects
|
||||
? defects.reduce((sum, d) => sum + (parseFloat(d.defect_hours) || 0), 0)
|
||||
: 0;
|
||||
|
||||
// 첫 번째 defect의 error_type_id를 대표값으로 (레거시 호환)
|
||||
const firstErrorTypeId = defects && defects.length > 0
|
||||
? (defects.find(d => d.error_type_id)?.error_type_id || null)
|
||||
: null;
|
||||
@@ -380,7 +307,6 @@ const saveReportDefectsService = async (reportId, defects) => {
|
||||
|
||||
/**
|
||||
* 부적합 원인 추가 (단일)
|
||||
* - issue_report_id 또는 error_type_id 중 하나 사용
|
||||
*/
|
||||
const addReportDefectService = async (reportId, defectData) => {
|
||||
const db = await getDb();
|
||||
@@ -396,7 +322,6 @@ const addReportDefectService = async (reportId, defectData) => {
|
||||
defectData.note || null
|
||||
]);
|
||||
|
||||
// 총 부적합 시간 업데이트
|
||||
await updateTotalErrorHours(reportId);
|
||||
|
||||
logger.info('부적합 원인 추가 성공', { reportId, defectId: result.insertId });
|
||||
@@ -413,7 +338,6 @@ const addReportDefectService = async (reportId, defectData) => {
|
||||
const removeReportDefectService = async (defectId) => {
|
||||
const db = await getDb();
|
||||
try {
|
||||
// report_id 먼저 조회
|
||||
const [defect] = await db.execute('SELECT report_id FROM work_report_defects WHERE defect_id = ?', [defectId]);
|
||||
if (defect.length === 0) {
|
||||
throw new NotFoundError('부적합 원인을 찾을 수 없습니다');
|
||||
@@ -421,10 +345,7 @@ const removeReportDefectService = async (defectId) => {
|
||||
|
||||
const reportId = defect[0].report_id;
|
||||
|
||||
// 삭제
|
||||
await db.execute('DELETE FROM work_report_defects WHERE defect_id = ?', [defectId]);
|
||||
|
||||
// 총 부적합 시간 업데이트
|
||||
await updateTotalErrorHours(reportId);
|
||||
|
||||
logger.info('부적합 원인 삭제 성공', { defectId, reportId });
|
||||
@@ -438,7 +359,6 @@ const removeReportDefectService = async (defectId) => {
|
||||
|
||||
/**
|
||||
* 총 부적합 시간 업데이트 헬퍼
|
||||
* - issue_report_id가 있는 경우도 고려
|
||||
*/
|
||||
const updateTotalErrorHours = async (reportId) => {
|
||||
const db = await getDb();
|
||||
@@ -450,8 +370,6 @@ const updateTotalErrorHours = async (reportId) => {
|
||||
|
||||
const totalErrorHours = result[0].total || 0;
|
||||
|
||||
// 첫 번째 부적합 원인의 error_type_id를 대표값으로 사용 (레거시 호환)
|
||||
// issue_report_id만 있는 경우 error_type_id는 null
|
||||
const [firstDefect] = await db.execute(`
|
||||
SELECT error_type_id FROM work_report_defects
|
||||
WHERE report_id = ? AND error_type_id IS NOT NULL
|
||||
@@ -480,7 +398,6 @@ module.exports = {
|
||||
updateWorkReportService,
|
||||
removeWorkReportService,
|
||||
getSummaryService,
|
||||
// 부적합 원인 관리
|
||||
getReportDefectsService,
|
||||
saveReportDefectsService,
|
||||
addReportDefectService,
|
||||
|
||||
@@ -19,7 +19,6 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('createWorkReportService', () => {
|
||||
it('단일 보고서를 성공적으로 생성해야 함', async () => {
|
||||
// Arrange
|
||||
const reportData = {
|
||||
report_date: '2025-12-11',
|
||||
worker_id: 1,
|
||||
@@ -29,46 +28,32 @@ describe('WorkReportService', () => {
|
||||
work_content: '기능 개발'
|
||||
};
|
||||
|
||||
// workReportModel.create가 콜백 형태이므로 모킹 설정
|
||||
workReportModel.create = jest.fn((data, callback) => {
|
||||
callback(null, 123); // insertId = 123
|
||||
});
|
||||
workReportModel.create.mockResolvedValue(123);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.createWorkReportService(reportData);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ workReport_ids: [123] });
|
||||
expect(workReportModel.create).toHaveBeenCalledTimes(1);
|
||||
expect(workReportModel.create).toHaveBeenCalledWith(
|
||||
reportData,
|
||||
expect.any(Function)
|
||||
);
|
||||
expect(workReportModel.create).toHaveBeenCalledWith(reportData);
|
||||
});
|
||||
|
||||
it('다중 보고서를 성공적으로 생성해야 함', async () => {
|
||||
// Arrange
|
||||
const reportsData = [
|
||||
{ report_date: '2025-12-11', worker_id: 1, work_hours: 8 },
|
||||
{ report_date: '2025-12-11', worker_id: 2, work_hours: 7 }
|
||||
];
|
||||
|
||||
let callCount = 0;
|
||||
workReportModel.create = jest.fn((data, callback) => {
|
||||
callCount++;
|
||||
callback(null, 100 + callCount);
|
||||
});
|
||||
workReportModel.create
|
||||
.mockResolvedValueOnce(101)
|
||||
.mockResolvedValueOnce(102);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.createWorkReportService(reportsData);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ workReport_ids: [101, 102] });
|
||||
expect(workReportModel.create).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('빈 배열이면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.createWorkReportService([]))
|
||||
.rejects.toThrow(ValidationError);
|
||||
|
||||
@@ -77,14 +62,10 @@ describe('WorkReportService', () => {
|
||||
});
|
||||
|
||||
it('DB 오류 시 DatabaseError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
const reportData = { report_date: '2025-12-11', worker_id: 1 };
|
||||
|
||||
workReportModel.create = jest.fn((data, callback) => {
|
||||
callback(new Error('DB connection failed'), null);
|
||||
});
|
||||
workReportModel.create.mockRejectedValue(new Error('DB connection failed'));
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.createWorkReportService(reportData))
|
||||
.rejects.toThrow(DatabaseError);
|
||||
});
|
||||
@@ -92,24 +73,18 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('getWorkReportsByDateService', () => {
|
||||
it('날짜로 보고서를 조회해야 함', async () => {
|
||||
// Arrange
|
||||
const date = '2025-12-11';
|
||||
const mockReports = mockWorkReports.filter(r => r.report_date === date);
|
||||
|
||||
workReportModel.getAllByDate = jest.fn((date, callback) => {
|
||||
callback(null, mockReports);
|
||||
});
|
||||
workReportModel.getAllByDate.mockResolvedValue(mockReports);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.getWorkReportsByDateService(date);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockReports);
|
||||
expect(workReportModel.getAllByDate).toHaveBeenCalledWith(date, expect.any(Function));
|
||||
expect(workReportModel.getAllByDate).toHaveBeenCalledWith(date);
|
||||
});
|
||||
|
||||
it('날짜가 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportsByDateService(null))
|
||||
.rejects.toThrow(ValidationError);
|
||||
|
||||
@@ -118,12 +93,8 @@ describe('WorkReportService', () => {
|
||||
});
|
||||
|
||||
it('DB 오류 시 DatabaseError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.getAllByDate = jest.fn((date, callback) => {
|
||||
callback(new Error('DB error'), null);
|
||||
});
|
||||
workReportModel.getAllByDate.mockRejectedValue(new Error('DB error'));
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportsByDateService('2025-12-11'))
|
||||
.rejects.toThrow(DatabaseError);
|
||||
});
|
||||
@@ -131,28 +102,19 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('getWorkReportByIdService', () => {
|
||||
it('ID로 보고서를 조회해야 함', async () => {
|
||||
// Arrange
|
||||
const mockReport = mockWorkReports[0];
|
||||
|
||||
workReportModel.getById = jest.fn((id, callback) => {
|
||||
callback(null, mockReport);
|
||||
});
|
||||
workReportModel.getById.mockResolvedValue(mockReport);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.getWorkReportByIdService(1);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockReport);
|
||||
expect(workReportModel.getById).toHaveBeenCalledWith(1, expect.any(Function));
|
||||
expect(workReportModel.getById).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('보고서가 없으면 NotFoundError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.getById = jest.fn((id, callback) => {
|
||||
callback(null, null);
|
||||
});
|
||||
workReportModel.getById.mockResolvedValue(null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportByIdService(999))
|
||||
.rejects.toThrow(NotFoundError);
|
||||
|
||||
@@ -161,7 +123,6 @@ describe('WorkReportService', () => {
|
||||
});
|
||||
|
||||
it('ID가 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportByIdService(null))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
@@ -169,38 +130,24 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('updateWorkReportService', () => {
|
||||
it('보고서를 성공적으로 수정해야 함', async () => {
|
||||
// Arrange
|
||||
const updateData = { work_hours: 9 };
|
||||
|
||||
workReportModel.update = jest.fn((id, data, callback) => {
|
||||
callback(null, 1); // affectedRows = 1
|
||||
});
|
||||
workReportModel.update.mockResolvedValue(1);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.updateWorkReportService(123, updateData);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ changes: 1 });
|
||||
expect(workReportModel.update).toHaveBeenCalledWith(
|
||||
123,
|
||||
updateData,
|
||||
expect.any(Function)
|
||||
);
|
||||
expect(workReportModel.update).toHaveBeenCalledWith(123, updateData);
|
||||
});
|
||||
|
||||
it('수정할 보고서가 없으면 NotFoundError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.update = jest.fn((id, data, callback) => {
|
||||
callback(null, 0); // affectedRows = 0
|
||||
});
|
||||
workReportModel.update.mockResolvedValue(0);
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.updateWorkReportService(999, {}))
|
||||
.rejects.toThrow(NotFoundError);
|
||||
});
|
||||
|
||||
it('ID가 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.updateWorkReportService(null, {}))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
@@ -208,32 +155,22 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('removeWorkReportService', () => {
|
||||
it('보고서를 성공적으로 삭제해야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.remove = jest.fn((id, callback) => {
|
||||
callback(null, 1);
|
||||
});
|
||||
workReportModel.remove.mockResolvedValue(1);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.removeWorkReportService(123);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ changes: 1 });
|
||||
expect(workReportModel.remove).toHaveBeenCalledWith(123, expect.any(Function));
|
||||
expect(workReportModel.remove).toHaveBeenCalledWith(123);
|
||||
});
|
||||
|
||||
it('삭제할 보고서가 없으면 NotFoundError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.remove = jest.fn((id, callback) => {
|
||||
callback(null, 0);
|
||||
});
|
||||
workReportModel.remove.mockResolvedValue(0);
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.removeWorkReportService(999))
|
||||
.rejects.toThrow(NotFoundError);
|
||||
});
|
||||
|
||||
it('ID가 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.removeWorkReportService(null))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
@@ -241,34 +178,23 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('getWorkReportsInRangeService', () => {
|
||||
it('기간으로 보고서를 조회해야 함', async () => {
|
||||
// Arrange
|
||||
const start = '2025-12-01';
|
||||
const end = '2025-12-31';
|
||||
|
||||
workReportModel.getByRange = jest.fn((start, end, callback) => {
|
||||
callback(null, mockWorkReports);
|
||||
});
|
||||
workReportModel.getByRange.mockResolvedValue(mockWorkReports);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.getWorkReportsInRangeService(start, end);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockWorkReports);
|
||||
expect(workReportModel.getByRange).toHaveBeenCalledWith(
|
||||
start,
|
||||
end,
|
||||
expect.any(Function)
|
||||
);
|
||||
expect(workReportModel.getByRange).toHaveBeenCalledWith(start, end);
|
||||
});
|
||||
|
||||
it('시작일이 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportsInRangeService(null, '2025-12-31'))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
|
||||
it('종료일이 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getWorkReportsInRangeService('2025-12-01', null))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
@@ -276,41 +202,30 @@ describe('WorkReportService', () => {
|
||||
|
||||
describe('getSummaryService', () => {
|
||||
it('월간 요약을 조회해야 함', async () => {
|
||||
// Arrange
|
||||
const year = '2025';
|
||||
const month = '12';
|
||||
|
||||
workReportModel.getByRange = jest.fn((start, end, callback) => {
|
||||
callback(null, mockWorkReports);
|
||||
});
|
||||
workReportModel.getByRange.mockResolvedValue(mockWorkReports);
|
||||
|
||||
// Act
|
||||
const result = await workReportService.getSummaryService(year, month);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockWorkReports);
|
||||
expect(workReportModel.getByRange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('연도가 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getSummaryService(null, '12'))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
|
||||
it('월이 없으면 ValidationError를 던져야 함', async () => {
|
||||
// Act & Assert
|
||||
await expect(workReportService.getSummaryService('2025', null))
|
||||
.rejects.toThrow(ValidationError);
|
||||
});
|
||||
|
||||
it('데이터가 없으면 NotFoundError를 던져야 함', async () => {
|
||||
// Arrange
|
||||
workReportModel.getByRange = jest.fn((start, end, callback) => {
|
||||
callback(null, []);
|
||||
});
|
||||
workReportModel.getByRange.mockResolvedValue([]);
|
||||
|
||||
// Act & Assert
|
||||
await expect(workReportService.getSummaryService('2025', '12'))
|
||||
.rejects.toThrow(NotFoundError);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user