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:
@@ -394,11 +394,11 @@ function openNewTbmModal() {
|
||||
}
|
||||
|
||||
// 입력자 자동 설정 (readonly)
|
||||
if (currentUser && currentUser.worker_id) {
|
||||
const worker = allWorkers.find(w => w.worker_id === currentUser.worker_id);
|
||||
if (currentUser && currentUser.user_id) {
|
||||
const worker = allWorkers.find(w => w.user_id === currentUser.user_id);
|
||||
if (worker) {
|
||||
document.getElementById('leaderName').textContent = worker.worker_name;
|
||||
document.getElementById('leaderId').value = worker.worker_id;
|
||||
document.getElementById('leaderId').value = worker.user_id;
|
||||
}
|
||||
} else if (currentUser && currentUser.name) {
|
||||
document.getElementById('leaderName').textContent = currentUser.name;
|
||||
@@ -444,7 +444,7 @@ async function renderNewTbmWorkerGrid() {
|
||||
todayAssignmentsMap = {};
|
||||
assignments.forEach(a => {
|
||||
if (a.sessions && a.sessions.length > 0) {
|
||||
todayAssignmentsMap[a.worker_id] = a;
|
||||
todayAssignmentsMap[a.user_id] = a;
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
@@ -454,8 +454,8 @@ async function renderNewTbmWorkerGrid() {
|
||||
}
|
||||
|
||||
grid.innerHTML = allWorkers.map(w => {
|
||||
const checked = selectedWorkersForNewTbm.has(w.worker_id) ? 'checked' : '';
|
||||
const assignment = todayAssignmentsMap[w.worker_id];
|
||||
const checked = selectedWorkersForNewTbm.has(w.user_id) ? 'checked' : '';
|
||||
const assignment = todayAssignmentsMap[w.user_id];
|
||||
const fullyAssigned = assignment && assignment.total_hours >= 8;
|
||||
const partiallyAssigned = assignment && assignment.total_hours > 0 && assignment.total_hours < 8;
|
||||
|
||||
@@ -474,9 +474,9 @@ async function renderNewTbmWorkerGrid() {
|
||||
}
|
||||
|
||||
return `
|
||||
<label class="tbm-worker-select-item ${checked ? 'selected' : ''}" data-wid="${w.worker_id}" style="${disabledStyle}">
|
||||
<input type="checkbox" class="new-tbm-worker-cb" data-worker-id="${w.worker_id}" ${checked} ${disabledAttr}
|
||||
onchange="toggleNewTbmWorker(${w.worker_id}, this.checked)">
|
||||
<label class="tbm-worker-select-item ${checked ? 'selected' : ''}" data-wid="${w.user_id}" style="${disabledStyle}">
|
||||
<input type="checkbox" class="new-tbm-worker-cb" data-user-id="${w.user_id}" ${checked} ${disabledAttr}
|
||||
onchange="toggleNewTbmWorker(${w.user_id}, this.checked)">
|
||||
<span class="tbm-worker-name">${escapeHtml(w.worker_name)}</span>
|
||||
<span class="tbm-worker-role">${escapeHtml(w.job_type || '작업자')}</span>
|
||||
${badgeHtml}
|
||||
@@ -511,9 +511,9 @@ window.toggleNewTbmWorker = toggleNewTbmWorker;
|
||||
|
||||
function selectAllNewTbmWorkers() {
|
||||
allWorkers.forEach(w => {
|
||||
const a = todayAssignmentsMap && todayAssignmentsMap[w.worker_id];
|
||||
const a = todayAssignmentsMap && todayAssignmentsMap[w.user_id];
|
||||
if (a && a.total_hours >= 8) return; // 종일 배정 제외
|
||||
selectedWorkersForNewTbm.add(w.worker_id);
|
||||
selectedWorkersForNewTbm.add(w.user_id);
|
||||
});
|
||||
document.querySelectorAll('.new-tbm-worker-cb').forEach(cb => {
|
||||
if (!cb.disabled) cb.checked = true;
|
||||
@@ -539,12 +539,12 @@ function populateLeaderSelect() {
|
||||
if (!leaderSelect) return;
|
||||
|
||||
// 로그인한 사용자가 작업자와 연결되어 있는지 확인
|
||||
if (currentUser && currentUser.worker_id) {
|
||||
if (currentUser && currentUser.user_id) {
|
||||
// 작업자와 연결된 경우: 자동으로 선택하고 비활성화
|
||||
const worker = allWorkers.find(w => w.worker_id === currentUser.worker_id);
|
||||
const worker = allWorkers.find(w => w.user_id === currentUser.user_id);
|
||||
if (worker) {
|
||||
const jobTypeText = worker.job_type ? ` (${escapeHtml(worker.job_type)})` : '';
|
||||
leaderSelect.innerHTML = `<option value="${escapeHtml(worker.worker_id)}" selected>${escapeHtml(worker.worker_name)}${jobTypeText}</option>`;
|
||||
leaderSelect.innerHTML = `<option value="${escapeHtml(worker.user_id)}" selected>${escapeHtml(worker.worker_name)}${jobTypeText}</option>`;
|
||||
leaderSelect.disabled = true;
|
||||
console.log('✅ 입력자 자동 설정:', worker.worker_name);
|
||||
} else {
|
||||
@@ -553,7 +553,7 @@ function populateLeaderSelect() {
|
||||
leaderSelect.disabled = true;
|
||||
}
|
||||
} else {
|
||||
// 관리자 계정 (worker_id가 없음): 드롭다운으로 선택 가능
|
||||
// 관리자 계정 (user_id가 없음): 드롭다운으로 선택 가능
|
||||
const leaders = allWorkers.filter(w =>
|
||||
w.job_type === 'leader' || w.job_type === '그룹장' || w.job_type === 'admin'
|
||||
);
|
||||
@@ -561,7 +561,7 @@ function populateLeaderSelect() {
|
||||
leaderSelect.innerHTML = '<option value="">입력자 선택...</option>' +
|
||||
leaders.map(w => {
|
||||
const jobTypeText = w.job_type ? ` (${escapeHtml(w.job_type)})` : '';
|
||||
return `<option value="${escapeHtml(w.worker_id)}">${escapeHtml(w.worker_name)}${jobTypeText}</option>`;
|
||||
return `<option value="${escapeHtml(w.user_id)}">${escapeHtml(w.worker_name)}${jobTypeText}</option>`;
|
||||
}).join('');
|
||||
leaderSelect.disabled = false;
|
||||
console.log('✅ 관리자: 입력자 선택 가능');
|
||||
@@ -646,8 +646,8 @@ async function saveTbmSession() {
|
||||
let leaderId = parseInt(document.getElementById('leaderId').value);
|
||||
|
||||
if (!leaderId || isNaN(leaderId)) {
|
||||
if (!currentUser.worker_id) {
|
||||
console.log('📝 관리자 계정: leader_id를 NULL로 설정');
|
||||
if (!currentUser.user_id) {
|
||||
console.log('📝 관리자 계정: leader_user_id를 NULL로 설정');
|
||||
leaderId = null;
|
||||
} else {
|
||||
console.error('❌ 입력자 설정 오류');
|
||||
@@ -658,7 +658,7 @@ async function saveTbmSession() {
|
||||
|
||||
const sessionData = {
|
||||
session_date: document.getElementById('sessionDate').value,
|
||||
leader_id: leaderId
|
||||
leader_user_id: leaderId
|
||||
};
|
||||
|
||||
if (!sessionData.session_date) {
|
||||
@@ -680,7 +680,7 @@ async function saveTbmSession() {
|
||||
for (const workerData of workerTaskList) {
|
||||
for (const taskLine of workerData.tasks) {
|
||||
members.push({
|
||||
worker_id: workerData.worker_id,
|
||||
user_id: workerData.user_id,
|
||||
project_id: taskLine.project_id || null,
|
||||
work_type_id: taskLine.work_type_id,
|
||||
task_id: taskLine.task_id,
|
||||
@@ -732,7 +732,7 @@ async function saveTbmSession() {
|
||||
const members = [];
|
||||
selectedWorkersForNewTbm.forEach(workerId => {
|
||||
members.push({
|
||||
worker_id: workerId,
|
||||
user_id: workerId,
|
||||
project_id: projectId,
|
||||
work_type_id: workTypeId,
|
||||
task_id: null,
|
||||
@@ -894,11 +894,11 @@ function openWorkerSelectionModal() {
|
||||
if (!workerCardGrid) return;
|
||||
|
||||
// 이미 추가된 작업자 ID 세트
|
||||
const addedWorkerIds = new Set(workerTaskList.map(w => w.worker_id));
|
||||
const addedWorkerIds = new Set(workerTaskList.map(w => w.user_id));
|
||||
|
||||
workerCardGrid.innerHTML = allWorkers.map(worker => {
|
||||
const isAdded = addedWorkerIds.has(worker.worker_id);
|
||||
const safeWorkerId = parseInt(worker.worker_id) || 0;
|
||||
const isAdded = addedWorkerIds.has(worker.user_id);
|
||||
const safeWorkerId = parseInt(worker.user_id) || 0;
|
||||
return `
|
||||
<div id="worker-card-${safeWorkerId}"
|
||||
onclick="toggleWorkerSelection(${safeWorkerId})"
|
||||
@@ -923,7 +923,7 @@ window.openWorkerSelectionModal = openWorkerSelectionModal;
|
||||
// 작업자 선택 토글
|
||||
function toggleWorkerSelection(workerId) {
|
||||
// 이미 추가된 작업자는 선택 불가
|
||||
const alreadyAdded = workerTaskList.some(w => w.worker_id === workerId);
|
||||
const alreadyAdded = workerTaskList.some(w => w.user_id === workerId);
|
||||
if (alreadyAdded) return;
|
||||
|
||||
const card = document.getElementById(`worker-card-${workerId}`);
|
||||
@@ -945,11 +945,11 @@ window.toggleWorkerSelection = toggleWorkerSelection;
|
||||
|
||||
// 전체 선택
|
||||
function selectAllWorkersInModal() {
|
||||
const addedWorkerIds = new Set(workerTaskList.map(w => w.worker_id));
|
||||
const addedWorkerIds = new Set(workerTaskList.map(w => w.user_id));
|
||||
allWorkers.forEach(worker => {
|
||||
if (!addedWorkerIds.has(worker.worker_id)) {
|
||||
selectedWorkersInModal.add(worker.worker_id);
|
||||
const card = document.getElementById(`worker-card-${worker.worker_id}`);
|
||||
if (!addedWorkerIds.has(worker.user_id)) {
|
||||
selectedWorkersInModal.add(worker.user_id);
|
||||
const card = document.getElementById(`worker-card-${worker.user_id}`);
|
||||
if (card) {
|
||||
card.style.borderColor = '#3b82f6';
|
||||
card.style.background = '#eff6ff';
|
||||
@@ -982,10 +982,10 @@ function confirmWorkerSelection() {
|
||||
}
|
||||
|
||||
selectedWorkersInModal.forEach(workerId => {
|
||||
const worker = allWorkers.find(w => w.worker_id === workerId);
|
||||
const worker = allWorkers.find(w => w.user_id === workerId);
|
||||
if (worker) {
|
||||
workerTaskList.push({
|
||||
worker_id: worker.worker_id,
|
||||
user_id: worker.user_id,
|
||||
worker_name: worker.worker_name,
|
||||
job_type: worker.job_type,
|
||||
tasks: [
|
||||
@@ -1970,16 +1970,16 @@ async function openTeamCompositionModal(sessionId) {
|
||||
|
||||
// 팀원별로 작업 그룹화
|
||||
teamMembers.forEach(member => {
|
||||
if (!workerMap.has(member.worker_id)) {
|
||||
workerMap.set(member.worker_id, {
|
||||
worker_id: member.worker_id,
|
||||
if (!workerMap.has(member.user_id)) {
|
||||
workerMap.set(member.user_id, {
|
||||
user_id: member.user_id,
|
||||
worker_name: member.worker_name,
|
||||
job_type: member.job_type,
|
||||
tasks: []
|
||||
});
|
||||
}
|
||||
|
||||
workerMap.get(member.worker_id).tasks.push({
|
||||
workerMap.get(member.user_id).tasks.push({
|
||||
task_line_id: generateUUID(),
|
||||
project_id: member.project_id,
|
||||
work_type_id: member.work_type_id,
|
||||
@@ -2003,7 +2003,7 @@ async function openTeamCompositionModal(sessionId) {
|
||||
// 입력자 표시
|
||||
if (session.leader_name) {
|
||||
document.getElementById('leaderName').value = `${session.leader_name} (${session.leader_job_type || ''})`;
|
||||
document.getElementById('leaderId').value = session.leader_id;
|
||||
document.getElementById('leaderId').value = session.leader_user_id;
|
||||
} else if (session.created_by_name) {
|
||||
document.getElementById('leaderName').value = `${session.created_by_name} (관리자)`;
|
||||
document.getElementById('leaderId').value = '';
|
||||
@@ -2026,7 +2026,7 @@ function updateSelectedWorkers() {
|
||||
selectedWorkers.clear();
|
||||
|
||||
document.querySelectorAll('.worker-checkbox:checked').forEach(cb => {
|
||||
selectedWorkers.add(parseInt(cb.dataset.workerId));
|
||||
selectedWorkers.add(parseInt(cb.dataset.userId));
|
||||
});
|
||||
|
||||
const selectedCount = document.getElementById('selectedCount');
|
||||
@@ -2038,7 +2038,7 @@ function updateSelectedWorkers() {
|
||||
selectedList.innerHTML = '<p style="margin: 0; color: #9ca3af; font-size: 0.875rem;">작업자를 선택해주세요</p>';
|
||||
} else {
|
||||
const selectedWorkersArray = Array.from(selectedWorkers).map(id => {
|
||||
const worker = allWorkers.find(w => w.worker_id === id);
|
||||
const worker = allWorkers.find(w => w.user_id === id);
|
||||
return worker ? `
|
||||
<span style="display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.25rem 0.75rem; background: #3b82f6; color: white; border-radius: 9999px; font-size: 0.875rem;">
|
||||
${worker.worker_name}
|
||||
@@ -2053,7 +2053,7 @@ window.updateSelectedWorkers = updateSelectedWorkers;
|
||||
|
||||
// 작업자 제거
|
||||
function removeWorker(workerId) {
|
||||
const checkbox = document.querySelector(`.worker-checkbox[data-worker-id="${workerId}"]`);
|
||||
const checkbox = document.querySelector(`.worker-checkbox[data-user-id="${workerId}"]`);
|
||||
if (checkbox) {
|
||||
checkbox.checked = false;
|
||||
updateSelectedWorkers();
|
||||
@@ -2094,7 +2094,7 @@ async function saveTeamComposition() {
|
||||
}
|
||||
|
||||
const members = Array.from(selectedWorkers).map(workerId => ({
|
||||
worker_id: workerId
|
||||
user_id: workerId
|
||||
}));
|
||||
|
||||
try {
|
||||
@@ -2427,7 +2427,7 @@ async function completeTbmSession() {
|
||||
}
|
||||
|
||||
attendanceData.push({
|
||||
worker_id: completeModalTeam[i].worker_id,
|
||||
user_id: completeModalTeam[i].user_id,
|
||||
attendance_type: type,
|
||||
attendance_hours: hours
|
||||
});
|
||||
@@ -2527,15 +2527,15 @@ async function viewTbmSession(sessionId) {
|
||||
// 작업자별로 그룹화
|
||||
const workerMap = new Map();
|
||||
team.forEach(member => {
|
||||
if (!workerMap.has(member.worker_id)) {
|
||||
workerMap.set(member.worker_id, {
|
||||
if (!workerMap.has(member.user_id)) {
|
||||
workerMap.set(member.user_id, {
|
||||
worker_name: member.worker_name,
|
||||
job_type: member.job_type,
|
||||
is_present: member.is_present,
|
||||
tasks: []
|
||||
});
|
||||
}
|
||||
workerMap.get(member.worker_id).tasks.push(member);
|
||||
workerMap.get(member.user_id).tasks.push(member);
|
||||
});
|
||||
|
||||
teamContainer.style.display = 'flex';
|
||||
@@ -2692,12 +2692,12 @@ async function openHandoverModal(sessionId) {
|
||||
const toLeaderSelect = document.getElementById('toLeaderId');
|
||||
const otherLeaders = allWorkers.filter(w =>
|
||||
(w.job_type === 'leader' || w.job_type === '그룹장' || w.job_type === 'admin') &&
|
||||
w.worker_id !== session.leader_id
|
||||
w.user_id !== session.leader_user_id
|
||||
);
|
||||
|
||||
toLeaderSelect.innerHTML = '<option value="">인수자 선택...</option>' +
|
||||
otherLeaders.map(w => `
|
||||
<option value="${w.worker_id}">${w.worker_name} (${w.job_type || ''})</option>
|
||||
<option value="${w.user_id}">${w.worker_name} (${w.job_type || ''})</option>
|
||||
`).join('');
|
||||
|
||||
// 인계할 팀원 목록
|
||||
@@ -2710,7 +2710,7 @@ async function openHandoverModal(sessionId) {
|
||||
onmouseover="this.style.background='#f9fafb'" onmouseout="this.style.background='white'">
|
||||
<input type="checkbox"
|
||||
class="handover-worker-checkbox"
|
||||
value="${member.worker_id}"
|
||||
value="${member.user_id}"
|
||||
checked
|
||||
style="width: 16px; height: 16px; cursor: pointer;">
|
||||
<span style="font-weight: 500; font-size: 0.875rem;">${member.worker_name}</span>
|
||||
@@ -2771,9 +2771,9 @@ async function saveHandover() {
|
||||
}
|
||||
|
||||
try {
|
||||
// 세션 정보 조회 (from_leader_id 가져오기)
|
||||
// 세션 정보 조회 (from_leader_user_id 가져오기)
|
||||
const sessionData = await window.TbmAPI.getSession(sessionId);
|
||||
const fromLeaderId = sessionData?.leader_id;
|
||||
const fromLeaderId = sessionData?.leader_user_id;
|
||||
|
||||
if (!fromLeaderId) {
|
||||
showToast('세션 정보를 찾을 수 없습니다.', 'error');
|
||||
@@ -2782,13 +2782,13 @@ async function saveHandover() {
|
||||
|
||||
const handoverData = {
|
||||
session_id: sessionId,
|
||||
from_leader_id: fromLeaderId,
|
||||
to_leader_id: toLeaderId,
|
||||
from_leader_user_id: fromLeaderId,
|
||||
to_leader_user_id: toLeaderId,
|
||||
handover_date: handoverDate,
|
||||
handover_time: handoverTime,
|
||||
reason: reason,
|
||||
handover_notes: handoverNotes,
|
||||
worker_ids: workerIds
|
||||
user_ids: workerIds
|
||||
};
|
||||
|
||||
const response = await window.TbmAPI.saveHandover(handoverData);
|
||||
@@ -2855,12 +2855,12 @@ async function executeSplit(memberIdx) {
|
||||
}
|
||||
try {
|
||||
await window.TbmAPI.updateTeamMember(splitModalSessionId, {
|
||||
worker_id: m.worker_id, project_id: m.project_id, work_type_id: m.work_type_id,
|
||||
user_id: m.user_id, project_id: m.project_id, work_type_id: m.work_type_id,
|
||||
task_id: m.task_id, workplace_category_id: m.workplace_category_id, workplace_id: m.workplace_id,
|
||||
work_detail: m.work_detail, is_present: true, work_hours: splitHours
|
||||
});
|
||||
await window.TbmAPI.splitAssignment(splitModalSessionId, {
|
||||
worker_id: m.worker_id, work_hours: currentHours - splitHours,
|
||||
user_id: m.user_id, work_hours: currentHours - splitHours,
|
||||
project_id: m.project_id, work_type_id: m.work_type_id
|
||||
});
|
||||
showToast(`${escapeHtml(m.worker_name)} 분할 완료: ${splitHours}h + ${currentHours - splitHours}h`, 'success');
|
||||
@@ -2934,8 +2934,8 @@ async function togglePullSessionMembers(sessionId, el) {
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; padding:0.375rem 0; border-bottom:1px solid #f9fafb;">
|
||||
<span>${escapeHtml(m.worker_name)} <span style="font-size:0.75rem; color:#6b7280;">(${hours}h)</span></span>
|
||||
<div style="display:flex; gap:0.25rem; align-items:center;">
|
||||
<input type="number" id="pull_h_${sessionId}_${m.worker_id}" step="0.5" min="0.5" max="${hours}" value="${hours}" style="width:60px; padding:0.25rem; border:1px solid #d1d5db; border-radius:0.25rem; font-size:0.75rem;">
|
||||
<button type="button" class="tbm-btn tbm-btn-primary" style="padding:0.25rem 0.5rem; font-size:0.75rem;" onclick="executePull(${sessionId}, ${m.worker_id}, '${escapeHtml(m.worker_name)}')">빼오기</button>
|
||||
<input type="number" id="pull_h_${sessionId}_${m.user_id}" step="0.5" min="0.5" max="${hours}" value="${hours}" style="width:60px; padding:0.25rem; border:1px solid #d1d5db; border-radius:0.25rem; font-size:0.75rem;">
|
||||
<button type="button" class="tbm-btn tbm-btn-primary" style="padding:0.25rem 0.5rem; font-size:0.75rem;" onclick="executePull(${sessionId}, ${m.user_id}, '${escapeHtml(m.worker_name)}')">빼오기</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('') || '<div style="color:#9ca3af; padding:0.25rem;">팀원 없음</div>';
|
||||
@@ -2954,7 +2954,7 @@ async function executePull(sourceSessionId, workerId, workerName) {
|
||||
try {
|
||||
const res = await window.TbmAPI.transfer({
|
||||
transfer_type: 'pull',
|
||||
worker_id: workerId,
|
||||
user_id: workerId,
|
||||
source_session_id: sourceSessionId,
|
||||
dest_session_id: pullModalSessionId,
|
||||
hours: hours
|
||||
|
||||
Reference in New Issue
Block a user