- 순찰/점검 기능 개선 (zone-detail 페이지 추가) - 출근/근태 시스템 개선 (연차 조회, 근무현황) - 작업분석 대분류 그룹화 및 마이그레이션 스크립트 - 모바일 네비게이션 UI 추가 - NAS 배포 도구 및 문서 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
422 lines
12 KiB
JavaScript
422 lines
12 KiB
JavaScript
/**
|
|
* vacationBalanceController.js
|
|
* 휴가 잔액 관련 컨트롤러
|
|
*/
|
|
|
|
const vacationBalanceModel = require('../models/vacationBalanceModel');
|
|
const vacationTypeModel = require('../models/vacationTypeModel');
|
|
|
|
const vacationBalanceController = {
|
|
/**
|
|
* 특정 작업자의 휴가 잔액 조회 (특정 연도)
|
|
* GET /api/vacation-balances/worker/:workerId/year/:year
|
|
*/
|
|
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
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('getByWorkerAndYear 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 모든 작업자의 휴가 잔액 조회 (특정 연도)
|
|
* GET /api/vacation-balances/year/:year
|
|
*/
|
|
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
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('getAllByYear 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 휴가 잔액 생성
|
|
* POST /api/vacation-balances
|
|
*/
|
|
async createBalance(req, res) {
|
|
try {
|
|
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,
|
|
message: '필수 필드가 누락되었습니다 (worker_id, vacation_type_id, year, total_days)'
|
|
});
|
|
}
|
|
|
|
// 중복 체크
|
|
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 }
|
|
});
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('createBalance 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 휴가 잔액 수정
|
|
* PUT /api/vacation-balances/:id
|
|
*/
|
|
async updateBalance(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { total_days, used_days, notes } = req.body;
|
|
|
|
const updateData = {};
|
|
if (total_days !== undefined) updateData.total_days = total_days;
|
|
if (used_days !== undefined) updateData.used_days = used_days;
|
|
if (notes !== undefined) updateData.notes = notes;
|
|
updateData.updated_at = new Date();
|
|
|
|
if (Object.keys(updateData).length === 1) {
|
|
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: '휴가 잔액을 수정하는 중 오류가 발생했습니다'
|
|
});
|
|
}
|
|
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: '휴가 잔액을 찾을 수 없습니다'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '휴가 잔액이 수정되었습니다'
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('updateBalance 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 휴가 잔액 삭제
|
|
* DELETE /api/vacation-balances/:id
|
|
*/
|
|
async deleteBalance(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
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: '휴가 잔액을 찾을 수 없습니다'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '휴가 잔액이 삭제되었습니다'
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('deleteBalance 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 근속년수 기반 연차 자동 계산 및 생성
|
|
* POST /api/vacation-balances/auto-calculate
|
|
*/
|
|
async autoCalculateAndCreate(req, res) {
|
|
try {
|
|
const { worker_id, hire_date, year } = req.body;
|
|
const created_by = req.user.user_id;
|
|
|
|
if (!worker_id || !hire_date || !year) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '필수 필드가 누락되었습니다 (worker_id, hire_date, year)'
|
|
});
|
|
}
|
|
|
|
// 연차 일수 계산
|
|
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 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
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('autoCalculateAndCreate 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 휴가 잔액 일괄 저장 (upsert)
|
|
* POST /api/vacation-balances/bulk-upsert
|
|
*/
|
|
async bulkUpsert(req, res) {
|
|
try {
|
|
const { balances } = req.body;
|
|
const created_by = req.user.user_id;
|
|
|
|
if (!balances || !Array.isArray(balances) || balances.length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '저장할 데이터가 없습니다'
|
|
});
|
|
}
|
|
|
|
const { getDb } = require('../dbPool');
|
|
const db = await getDb();
|
|
|
|
let successCount = 0;
|
|
let errorCount = 0;
|
|
|
|
for (const balance of balances) {
|
|
const { worker_id, vacation_type_id, year, total_days, notes } = balance;
|
|
|
|
if (!worker_id || !vacation_type_id || !year || total_days === undefined) {
|
|
errorCount++;
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
// Upsert 쿼리
|
|
const query = `
|
|
INSERT INTO vacation_balance_details
|
|
(worker_id, vacation_type_id, year, total_days, used_days, notes, created_by)
|
|
VALUES (?, ?, ?, ?, 0, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
total_days = VALUES(total_days),
|
|
notes = VALUES(notes),
|
|
updated_at = NOW()
|
|
`;
|
|
|
|
await db.query(query, [worker_id, vacation_type_id, year, total_days, notes || null, created_by]);
|
|
successCount++;
|
|
} catch (err) {
|
|
console.error('휴가 잔액 저장 오류:', err);
|
|
errorCount++;
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `${successCount}건 저장 완료${errorCount > 0 ? `, ${errorCount}건 실패` : ''}`,
|
|
data: { successCount, errorCount }
|
|
});
|
|
} catch (error) {
|
|
console.error('bulkUpsert 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 작업자의 사용 가능한 휴가 일수 조회
|
|
* GET /api/vacation-balances/worker/:workerId/year/:year/available
|
|
*/
|
|
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
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('getAvailableDays 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다'
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
module.exports = vacationBalanceController;
|