Files
tk-factory-services/system1-factory/api/controllers/dashboardController.js
Hyungi Ahn ba2e3481e9 feat(vacation): 이월연차 만료 시스템 + 대시보드 합산 개선
- createBalance/bulkUpsert에서 CARRY_OVER expires_at 자동 설정
  (carry_over_expiry_month 설정 기반, 기본값 2월말)
- deductByPriority/deductDays 쿼리에 만료 필터 추가
  (expires_at >= CURDATE() 조건, 만료된 이월 차감 제외)
- 대시보드 합산에서 만료된 잔액 제외
- DB 보정 완료: CARRY_OVER 8건 expires_at 설정,
  황인용/최광욱 소급 재계산 (이월 우선 차감 반영)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 12:27:17 +09:00

93 lines
3.4 KiB
JavaScript

/**
* 대시보드 컨트롤러
* Sprint 003 — 개인 요약 API
*/
const DashboardModel = require('../models/dashboardModel');
const logger = require('../../shared/utils/logger');
const DashboardController = {
/**
* GET /api/dashboard/my-summary
* 연차 잔여 + 월간 연장근로 + 접근 가능 페이지
*/
getMySummary: async (req, res) => {
try {
const userId = req.user.user_id || req.user.id;
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
// 1단계: 사용자 정보 먼저 조회 (worker_id 필요)
const userInfo = await DashboardModel.getUserInfo(userId);
if (!userInfo) {
return res.status(404).json({ success: false, message: '사용자 정보를 찾을 수 없습니다.' });
}
const departmentId = userInfo.department_id;
const role = userInfo.role;
// 2단계: 나머지 3개 병렬 조회 (연차: sp_vacation_balances from user_id)
const [vacationRows, overtime, quickAccess] = await Promise.all([
DashboardModel.getVacationBalance(userId, year),
DashboardModel.getMonthlyOvertime(userId, year, month),
DashboardModel.getQuickAccess(userId, departmentId, role)
]);
// 연차 응답 가공
const details = vacationRows.map(v => ({
type_name: v.type_name,
type_code: v.type_code,
balance_type: v.balance_type || 'AUTO',
expires_at: v.expires_at || null,
total: parseFloat(v.total_days) || 0,
used: parseFloat(v.used_days) || 0,
remaining: parseFloat(v.remaining_days) || 0
}));
// 만료되지 않은 balance만 합산 (만료된 이월연차 제외)
const today = new Date().toISOString().substring(0, 10);
const activeRows = vacationRows.filter(v => !v.expires_at || v.expires_at >= today);
const totalDays = activeRows.reduce((s, v) => s + (parseFloat(v.total_days) || 0), 0);
const usedDays = activeRows.reduce((s, v) => s + (parseFloat(v.used_days) || 0), 0);
const remainingDays = totalDays - usedDays;
res.json({
success: true,
data: {
user: {
user_id: userInfo.user_id,
name: userInfo.name,
worker_name: userInfo.worker_name || userInfo.name,
job_type: userInfo.job_type || '',
department_name: userInfo.department_name,
department_id: userInfo.department_id,
role: userInfo.role
},
vacation: {
year,
total_days: totalDays,
used_days: usedDays,
remaining_days: remainingDays,
details
},
overtime: {
year,
month,
total_overtime_hours: parseFloat(overtime.total_overtime_hours) || 0,
overtime_days: parseInt(overtime.overtime_days) || 0,
total_work_days: parseInt(overtime.total_work_days) || 0,
total_work_hours: parseFloat(overtime.total_work_hours) || 0,
avg_daily_hours: parseFloat(parseFloat(overtime.avg_daily_hours || 0).toFixed(1))
},
quick_access: quickAccess
}
});
} catch (err) {
logger.error('대시보드 요약 조회 오류:', err);
res.status(500).json({ success: false, message: '대시보드 데이터 조회 중 오류가 발생했습니다.', error: err.message });
}
}
};
module.exports = DashboardController;