refactor: worker_id → user_id 전체 마이그레이션 (Phase 1-4)
sso_users.user_id를 단일 식별자로 통합. JWT에서 worker_id 제거, department_id/is_production 추가. 백엔드 15개 모델, 11개 컨트롤러, 4개 서비스, 7개 라우트, 프론트엔드 32+ JS/11+ HTML 변환. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ const { getDb } = require('../dbPool');
|
||||
|
||||
class AttendanceModel {
|
||||
// 일일 근태 기록 조회
|
||||
static async getDailyAttendanceRecords(date, workerId = null) {
|
||||
static async getDailyAttendanceRecords(date, userId = null) {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
@@ -15,7 +15,7 @@ class AttendanceModel {
|
||||
vt.type_code as vacation_type_code,
|
||||
vt.deduct_days as vacation_days
|
||||
FROM daily_attendance_records dar
|
||||
LEFT JOIN workers w ON dar.worker_id = w.worker_id
|
||||
LEFT JOIN workers w ON dar.user_id = w.user_id
|
||||
LEFT JOIN work_attendance_types wat ON dar.attendance_type_id = wat.id
|
||||
LEFT JOIN vacation_types vt ON dar.vacation_type_id = vt.id
|
||||
WHERE dar.record_date = ?
|
||||
@@ -23,9 +23,9 @@ class AttendanceModel {
|
||||
|
||||
const params = [date];
|
||||
|
||||
if (workerId) {
|
||||
query += ' AND dar.worker_id = ?';
|
||||
params.push(workerId);
|
||||
if (userId) {
|
||||
query += ' AND dar.user_id = ?';
|
||||
params.push(userId);
|
||||
}
|
||||
|
||||
query += ' ORDER BY w.worker_name';
|
||||
@@ -35,7 +35,7 @@ class AttendanceModel {
|
||||
}
|
||||
|
||||
// 기간별 근태 기록 조회 (월별 조회용)
|
||||
static async getDailyRecords(startDate, endDate, workerId = null) {
|
||||
static async getDailyRecords(startDate, endDate, userId = null) {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
@@ -48,7 +48,7 @@ class AttendanceModel {
|
||||
vt.type_code as vacation_type_code,
|
||||
vt.deduct_days as vacation_days
|
||||
FROM daily_attendance_records dar
|
||||
LEFT JOIN workers w ON dar.worker_id = w.worker_id
|
||||
LEFT JOIN workers w ON dar.user_id = w.user_id
|
||||
LEFT JOIN work_attendance_types wat ON dar.attendance_type_id = wat.id
|
||||
LEFT JOIN vacation_types vt ON dar.vacation_type_id = vt.id
|
||||
WHERE dar.record_date BETWEEN ? AND ?
|
||||
@@ -56,9 +56,9 @@ class AttendanceModel {
|
||||
|
||||
const params = [startDate, endDate];
|
||||
|
||||
if (workerId) {
|
||||
query += ' AND dar.worker_id = ?';
|
||||
params.push(workerId);
|
||||
if (userId) {
|
||||
query += ' AND dar.user_id = ?';
|
||||
params.push(userId);
|
||||
}
|
||||
|
||||
query += ' ORDER BY dar.record_date ASC';
|
||||
@@ -68,17 +68,17 @@ class AttendanceModel {
|
||||
}
|
||||
|
||||
// 작업 보고서와 근태 기록 동기화 (시간 합산 및 상태 업데이트)
|
||||
static async syncWithWorkReports(workerId, date) {
|
||||
static async syncWithWorkReports(userId, date) {
|
||||
const db = await getDb();
|
||||
|
||||
// 1. 해당 날짜의 총 작업 시간 계산
|
||||
const [reportStats] = await db.execute(`
|
||||
SELECT
|
||||
SELECT
|
||||
COALESCE(SUM(work_hours), 0) as total_hours,
|
||||
COUNT(*) as report_count
|
||||
FROM daily_work_reports
|
||||
WHERE worker_id = ? AND report_date = ?
|
||||
`, [workerId, date]);
|
||||
WHERE user_id = ? AND report_date = ?
|
||||
`, [userId, date]);
|
||||
|
||||
const totalHours = parseFloat(reportStats[0].total_hours || 0);
|
||||
const reportCount = reportStats[0].report_count;
|
||||
@@ -110,8 +110,8 @@ class AttendanceModel {
|
||||
// 3. 기록 업데이트 (휴가 정보는 유지)
|
||||
// 기존 기록 조회
|
||||
const [existing] = await db.execute(
|
||||
'SELECT id, vacation_type_id FROM daily_attendance_records WHERE worker_id = ? AND record_date = ?',
|
||||
[workerId, date]
|
||||
'SELECT id, vacation_type_id FROM daily_attendance_records WHERE user_id = ? AND record_date = ?',
|
||||
[userId, date]
|
||||
);
|
||||
|
||||
if (existing.length > 0) {
|
||||
@@ -138,9 +138,9 @@ class AttendanceModel {
|
||||
// 생성자가 명확하지 않으므로 시스템(1) 또는 알 수 없음 처리
|
||||
await db.execute(`
|
||||
INSERT INTO daily_attendance_records
|
||||
(record_date, worker_id, total_work_hours, attendance_type_id, status, created_by)
|
||||
(record_date, user_id, total_work_hours, attendance_type_id, status, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, 1)
|
||||
`, [date, workerId, totalHours, typeId, status]);
|
||||
`, [date, userId, totalHours, typeId, status]);
|
||||
|
||||
return { synced: true, totalHours, status, created: true };
|
||||
}
|
||||
@@ -152,14 +152,14 @@ class AttendanceModel {
|
||||
|
||||
// 1. 활성 작업자 조회
|
||||
const [workers] = await db.execute(
|
||||
'SELECT worker_id FROM workers WHERE status = "active"' // is_active check not needed as status covers it based on previous fix? Wait, previous fix used status='active'.
|
||||
'SELECT user_id FROM workers WHERE status = "active" AND user_id IS NOT NULL'
|
||||
);
|
||||
|
||||
if (workers.length === 0) return { inserted: 0 };
|
||||
|
||||
// 2. 일일 근태 레코드 일괄 생성 (이미 존재하면 무시)
|
||||
// VALUES (...), (...), ...
|
||||
const values = workers.map(w => [date, w.worker_id, 'incomplete', createdBy]);
|
||||
const values = workers.map(w => [date, w.user_id, 'incomplete', createdBy]);
|
||||
|
||||
// Bulk INSERT IGNORE
|
||||
// Note: mysql2 execute doesn't support nested arrays for bulk insert easily with placeholder ?
|
||||
@@ -175,10 +175,10 @@ class AttendanceModel {
|
||||
|
||||
for (const w of workers) {
|
||||
const [result] = await conn.execute(`
|
||||
INSERT IGNORE INTO daily_attendance_records
|
||||
(record_date, worker_id, status, created_by)
|
||||
INSERT IGNORE INTO daily_attendance_records
|
||||
(record_date, user_id, status, created_by)
|
||||
VALUES (?, ?, 'incomplete', ?)
|
||||
`, [date, w.worker_id, createdBy]);
|
||||
`, [date, w.user_id, createdBy]);
|
||||
|
||||
insertedCount += result.affectedRows;
|
||||
}
|
||||
@@ -200,7 +200,7 @@ class AttendanceModel {
|
||||
|
||||
const {
|
||||
record_date,
|
||||
worker_id,
|
||||
user_id,
|
||||
total_work_hours = 8,
|
||||
work_attendance_type_id = 1,
|
||||
vacation_type_id = null,
|
||||
@@ -212,8 +212,8 @@ class AttendanceModel {
|
||||
|
||||
// 기존 기록 확인
|
||||
const [existing] = await db.execute(
|
||||
'SELECT id FROM daily_attendance_records WHERE worker_id = ? AND record_date = ?',
|
||||
[worker_id, record_date]
|
||||
'SELECT id FROM daily_attendance_records WHERE user_id = ? AND record_date = ?',
|
||||
[user_id, record_date]
|
||||
);
|
||||
|
||||
if (existing.length > 0) {
|
||||
@@ -240,12 +240,12 @@ class AttendanceModel {
|
||||
// 생성
|
||||
const [result] = await db.execute(`
|
||||
INSERT INTO daily_attendance_records (
|
||||
record_date, worker_id, total_work_hours, attendance_type_id,
|
||||
record_date, user_id, total_work_hours, attendance_type_id,
|
||||
vacation_type_id, is_overtime_approved, created_by
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`, [
|
||||
record_date,
|
||||
worker_id,
|
||||
user_id,
|
||||
total_work_hours,
|
||||
attendance_type_id,
|
||||
vacation_type_id,
|
||||
@@ -263,8 +263,8 @@ class AttendanceModel {
|
||||
|
||||
// 모든 작업자와 해당 날짜의 근태 기록을 조회
|
||||
const [rows] = await db.execute(`
|
||||
SELECT
|
||||
w.worker_id,
|
||||
SELECT
|
||||
w.user_id,
|
||||
w.worker_name,
|
||||
w.job_type,
|
||||
COALESCE(dar.total_work_hours, 0) as total_work_hours,
|
||||
@@ -277,16 +277,16 @@ class AttendanceModel {
|
||||
vt.type_code as vacation_type_code,
|
||||
dar.notes,
|
||||
-- 작업 건수 계산
|
||||
(SELECT COUNT(*) FROM daily_work_reports dwr
|
||||
WHERE dwr.worker_id = w.worker_id AND dwr.report_date = ?) as work_count,
|
||||
(SELECT COUNT(*) FROM daily_work_reports dwr
|
||||
WHERE dwr.user_id = w.user_id AND dwr.report_date = ?) as work_count,
|
||||
-- 오류 건수 계산
|
||||
(SELECT COUNT(*) FROM daily_work_reports dwr
|
||||
WHERE dwr.worker_id = w.worker_id AND dwr.report_date = ? AND dwr.work_status_id = 2) as error_count
|
||||
(SELECT COUNT(*) FROM daily_work_reports dwr
|
||||
WHERE dwr.user_id = w.user_id AND dwr.report_date = ? AND dwr.work_status_id = 2) as error_count
|
||||
FROM workers w
|
||||
LEFT JOIN daily_attendance_records dar ON w.worker_id = dar.worker_id AND dar.record_date = ?
|
||||
LEFT JOIN daily_attendance_records dar ON w.user_id = dar.user_id AND dar.record_date = ?
|
||||
LEFT JOIN work_attendance_types wat ON dar.attendance_type_id = wat.id
|
||||
LEFT JOIN vacation_types vt ON dar.vacation_type_id = vt.id
|
||||
WHERE w.is_active = TRUE
|
||||
WHERE w.status = 'active' AND w.user_id IS NOT NULL
|
||||
ORDER BY w.worker_name
|
||||
`, [date, date, date]);
|
||||
|
||||
@@ -294,7 +294,7 @@ class AttendanceModel {
|
||||
}
|
||||
|
||||
// 휴가 처리
|
||||
static async processVacation(workerId, date, vacationType, createdBy) {
|
||||
static async processVacation(userId, date, vacationType, createdBy) {
|
||||
const db = await getDb();
|
||||
|
||||
// 휴가 유형 정보 조회
|
||||
@@ -312,9 +312,9 @@ class AttendanceModel {
|
||||
// 현재 작업 시간 조회
|
||||
const [workHours] = await db.execute(`
|
||||
SELECT COALESCE(SUM(work_hours), 0) as total_hours
|
||||
FROM daily_work_reports
|
||||
WHERE worker_id = ? AND report_date = ?
|
||||
`, [workerId, date]);
|
||||
FROM daily_work_reports
|
||||
WHERE user_id = ? AND report_date = ?
|
||||
`, [userId, date]);
|
||||
|
||||
const currentHours = parseFloat(workHours[0].total_hours);
|
||||
// deduct_days를 시간으로 변환 (1일 = 8시간)
|
||||
@@ -338,12 +338,12 @@ class AttendanceModel {
|
||||
// 휴가 작업 기록 생성 (프로젝트 ID 13 = "연차/휴무", work_type_id 1 = 기본)
|
||||
await db.execute(`
|
||||
INSERT INTO daily_work_reports (
|
||||
report_date, worker_id, project_id, work_type_id, work_status_id,
|
||||
report_date, user_id, project_id, work_type_id, work_status_id,
|
||||
work_hours, description, created_by
|
||||
) VALUES (?, ?, 13, 1, 1, ?, ?, ?)
|
||||
`, [
|
||||
date,
|
||||
workerId,
|
||||
userId,
|
||||
vacationHours,
|
||||
`${vacationTypeInfo.type_name} 처리`,
|
||||
createdBy
|
||||
@@ -352,7 +352,7 @@ class AttendanceModel {
|
||||
// 근태 기록 업데이트
|
||||
const attendanceData = {
|
||||
record_date: date,
|
||||
worker_id: workerId,
|
||||
user_id: userId,
|
||||
total_work_hours: totalHours,
|
||||
work_attendance_type_id: attendanceTypes[0]?.id,
|
||||
vacation_type_id: vacationTypeInfo.id,
|
||||
@@ -364,19 +364,19 @@ class AttendanceModel {
|
||||
}
|
||||
|
||||
// 초과근무 승인
|
||||
static async approveOvertime(workerId, date, approvedBy) {
|
||||
static async approveOvertime(userId, date, approvedBy) {
|
||||
const db = await getDb();
|
||||
|
||||
const [result] = await db.execute(`
|
||||
UPDATE daily_attendance_records
|
||||
SET
|
||||
UPDATE daily_attendance_records
|
||||
SET
|
||||
overtime_approved = TRUE,
|
||||
overtime_approved_by = ?,
|
||||
overtime_approved_at = CURRENT_TIMESTAMP,
|
||||
updated_by = ?,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE worker_id = ? AND record_date = ?
|
||||
`, [approvedBy, approvedBy, workerId, date]);
|
||||
WHERE user_id = ? AND record_date = ?
|
||||
`, [approvedBy, approvedBy, userId, date]);
|
||||
|
||||
return result.affectedRows > 0;
|
||||
}
|
||||
@@ -400,24 +400,24 @@ class AttendanceModel {
|
||||
}
|
||||
|
||||
// 작업자 휴가 잔여 조회
|
||||
static async getWorkerVacationBalance(workerId, year = null) {
|
||||
static async getWorkerVacationBalance(userId, year = null) {
|
||||
const db = await getDb();
|
||||
const currentYear = year || new Date().getFullYear();
|
||||
|
||||
const [rows] = await db.execute(`
|
||||
SELECT id, worker_id, year, total_annual_leave, used_annual_leave, notes, created_at, updated_at FROM worker_vacation_balance
|
||||
WHERE worker_id = ? AND year = ?
|
||||
`, [workerId, currentYear]);
|
||||
SELECT id, user_id, year, total_annual_leave, used_annual_leave, notes, created_at, updated_at FROM worker_vacation_balance
|
||||
WHERE user_id = ? AND year = ?
|
||||
`, [userId, currentYear]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
// 기본 연차 생성 (15일)
|
||||
await db.execute(`
|
||||
INSERT INTO worker_vacation_balance (worker_id, year, total_annual_leave)
|
||||
INSERT INTO worker_vacation_balance (user_id, year, total_annual_leave)
|
||||
VALUES (?, ?, 15.0)
|
||||
`, [workerId, currentYear]);
|
||||
`, [userId, currentYear]);
|
||||
|
||||
return {
|
||||
worker_id: workerId,
|
||||
user_id: userId,
|
||||
year: currentYear,
|
||||
total_annual_leave: 15.0,
|
||||
used_annual_leave: 0,
|
||||
@@ -429,14 +429,14 @@ class AttendanceModel {
|
||||
}
|
||||
|
||||
// 월별 근태 통계
|
||||
static async getMonthlyAttendanceStats(year, month, workerId = null) {
|
||||
static async getMonthlyAttendanceStats(year, month, userId = null) {
|
||||
const db = await getDb();
|
||||
|
||||
// work_attendance_types: 1=NORMAL, 2=LATE, 3=EARLY_LEAVE, 4=ABSENT, 5=VACATION
|
||||
// vacation_types: 1=ANNUAL(연차), 2=HALF_ANNUAL(반차), 3=SICK(병가), 4=SPECIAL(경조사)
|
||||
let query = `
|
||||
SELECT
|
||||
w.worker_id,
|
||||
w.user_id,
|
||||
w.worker_name,
|
||||
COUNT(CASE WHEN dar.attendance_type_id = 1 AND (dar.is_overtime_approved = 0 OR dar.is_overtime_approved IS NULL) THEN 1 END) as regular_days,
|
||||
COUNT(CASE WHEN dar.is_overtime_approved = 1 OR dar.total_work_hours > 8 THEN 1 END) as overtime_days,
|
||||
@@ -446,19 +446,19 @@ class AttendanceModel {
|
||||
COALESCE(SUM(dar.total_work_hours), 0) as total_work_hours,
|
||||
COALESCE(AVG(dar.total_work_hours), 0) as avg_work_hours
|
||||
FROM workers w
|
||||
LEFT JOIN daily_attendance_records dar ON w.worker_id = dar.worker_id
|
||||
LEFT JOIN daily_attendance_records dar ON w.user_id = dar.user_id
|
||||
AND YEAR(dar.record_date) = ? AND MONTH(dar.record_date) = ?
|
||||
WHERE w.employment_status = 'employed'
|
||||
`;
|
||||
|
||||
const params = [year, month];
|
||||
|
||||
if (workerId) {
|
||||
query += ' AND w.worker_id = ?';
|
||||
params.push(workerId);
|
||||
if (userId) {
|
||||
query += ' AND w.user_id = ?';
|
||||
params.push(userId);
|
||||
}
|
||||
|
||||
query += ' GROUP BY w.worker_id, w.worker_name ORDER BY w.worker_name';
|
||||
query += ' GROUP BY w.user_id, w.worker_name ORDER BY w.worker_name';
|
||||
|
||||
const [rows] = await db.execute(query, params);
|
||||
return rows;
|
||||
@@ -467,12 +467,12 @@ class AttendanceModel {
|
||||
// 출근 체크 기록 생성 또는 업데이트
|
||||
static async upsertCheckin(checkinData) {
|
||||
const db = await getDb();
|
||||
const { worker_id, record_date, is_present } = checkinData;
|
||||
const { user_id, record_date, is_present } = checkinData;
|
||||
|
||||
// 해당 날짜에 기록이 있는지 확인
|
||||
const [existing] = await db.execute(
|
||||
'SELECT id FROM daily_attendance_records WHERE worker_id = ? AND record_date = ?',
|
||||
[worker_id, record_date]
|
||||
'SELECT id FROM daily_attendance_records WHERE user_id = ? AND record_date = ?',
|
||||
[user_id, record_date]
|
||||
);
|
||||
|
||||
if (existing.length > 0) {
|
||||
@@ -486,9 +486,9 @@ class AttendanceModel {
|
||||
// 새로 생성 (기본값으로)
|
||||
const [result] = await db.execute(
|
||||
`INSERT INTO daily_attendance_records
|
||||
(worker_id, record_date, is_present, attendance_type_id, created_by)
|
||||
(user_id, record_date, is_present, attendance_type_id, created_by)
|
||||
VALUES (?, ?, ?, 1, 1)`,
|
||||
[worker_id, record_date, is_present]
|
||||
[user_id, record_date, is_present]
|
||||
);
|
||||
return result.insertId;
|
||||
}
|
||||
@@ -499,7 +499,7 @@ class AttendanceModel {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
w.worker_id,
|
||||
w.user_id,
|
||||
w.worker_name,
|
||||
w.job_type,
|
||||
w.employment_status,
|
||||
@@ -512,9 +512,9 @@ class AttendanceModel {
|
||||
vr.days_used as vacation_days
|
||||
FROM workers w
|
||||
LEFT JOIN daily_attendance_records dar
|
||||
ON w.worker_id = dar.worker_id AND dar.record_date = ?
|
||||
ON w.user_id = dar.user_id AND dar.record_date = ?
|
||||
LEFT JOIN vacation_requests vr
|
||||
ON w.worker_id = vr.worker_id
|
||||
ON w.user_id = vr.user_id
|
||||
AND ? BETWEEN vr.start_date AND vr.end_date
|
||||
AND vr.status = 'approved'
|
||||
LEFT JOIN vacation_types vt ON vr.vacation_type_id = vt.id
|
||||
|
||||
Reference in New Issue
Block a user