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:
@@ -1,105 +0,0 @@
|
||||
/**
|
||||
* 마이그레이션: TBM 기반 작업보고서의 work_type_id를 task_id로 수정
|
||||
*
|
||||
* 문제: TBM에서 작업보고서 생성 시 work_type_id(공정 ID)가 저장됨
|
||||
* 해결: tbm_team_assignments 테이블의 task_id로 업데이트
|
||||
*
|
||||
* 실행: node db/migrations/20260205_fix_work_type_id_data.js
|
||||
*/
|
||||
|
||||
const { getDb } = require('../../dbPool');
|
||||
|
||||
async function migrate() {
|
||||
const db = await getDb();
|
||||
|
||||
console.log('🔄 TBM 기반 작업보고서 work_type_id 수정 시작...\n');
|
||||
|
||||
try {
|
||||
// 1. 수정 대상 확인 (TBM 기반이면서 work_type_id가 task_id와 다른 경우)
|
||||
const [checkResult] = await db.query(`
|
||||
SELECT
|
||||
dwr.id,
|
||||
dwr.work_type_id as current_work_type_id,
|
||||
ta.task_id as correct_task_id,
|
||||
ta.work_type_id as tbm_work_type_id,
|
||||
w.worker_name,
|
||||
dwr.report_date
|
||||
FROM daily_work_reports dwr
|
||||
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
|
||||
INNER JOIN workers w ON dwr.worker_id = w.worker_id
|
||||
WHERE dwr.tbm_assignment_id IS NOT NULL
|
||||
AND ta.task_id IS NOT NULL
|
||||
AND dwr.work_type_id != ta.task_id
|
||||
ORDER BY dwr.report_date DESC
|
||||
`);
|
||||
|
||||
console.log(`📊 수정 대상: ${checkResult.length}개 레코드\n`);
|
||||
|
||||
if (checkResult.length === 0) {
|
||||
console.log('✅ 수정할 데이터가 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 수정 대상 샘플 출력
|
||||
console.log('📋 수정 대상 샘플 (최대 10개):');
|
||||
console.log('─'.repeat(80));
|
||||
checkResult.slice(0, 10).forEach(row => {
|
||||
console.log(` ID: ${row.id} | ${row.worker_name} | ${row.report_date}`);
|
||||
console.log(` 현재 work_type_id: ${row.current_work_type_id} → 올바른 task_id: ${row.correct_task_id}`);
|
||||
});
|
||||
if (checkResult.length > 10) {
|
||||
console.log(` ... 외 ${checkResult.length - 10}개`);
|
||||
}
|
||||
console.log('─'.repeat(80));
|
||||
|
||||
// 2. 업데이트 실행
|
||||
const [updateResult] = await db.query(`
|
||||
UPDATE daily_work_reports dwr
|
||||
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
|
||||
SET dwr.work_type_id = ta.task_id
|
||||
WHERE dwr.tbm_assignment_id IS NOT NULL
|
||||
AND ta.task_id IS NOT NULL
|
||||
AND dwr.work_type_id != ta.task_id
|
||||
`);
|
||||
|
||||
console.log(`\n✅ 업데이트 완료: ${updateResult.affectedRows}개 레코드 수정됨`);
|
||||
|
||||
// 3. 수정 결과 확인
|
||||
const [verifyResult] = await db.query(`
|
||||
SELECT
|
||||
dwr.id,
|
||||
dwr.work_type_id,
|
||||
ta.task_id,
|
||||
t.task_name,
|
||||
wt.name as work_type_name
|
||||
FROM daily_work_reports dwr
|
||||
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
|
||||
LEFT JOIN tasks t ON dwr.work_type_id = t.task_id
|
||||
LEFT JOIN work_types wt ON t.work_type_id = wt.id
|
||||
WHERE dwr.tbm_assignment_id IS NOT NULL
|
||||
LIMIT 5
|
||||
`);
|
||||
|
||||
console.log('\n📋 수정 후 샘플 확인:');
|
||||
console.log('─'.repeat(80));
|
||||
verifyResult.forEach(row => {
|
||||
console.log(` ID: ${row.id} | work_type_id: ${row.work_type_id} | task: ${row.task_name || 'N/A'} | 공정: ${row.work_type_name || 'N/A'}`);
|
||||
});
|
||||
console.log('─'.repeat(80));
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 마이그레이션 실패:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 실행
|
||||
migrate()
|
||||
.then(() => {
|
||||
console.log('\n🎉 마이그레이션 완료!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('\n💥 마이그레이션 실패:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* worker_id → user_id 통합 마이그레이션 (Phase 1)
|
||||
*
|
||||
* 비파괴적 추가: 기존 worker_id 컬럼은 유지하면서 user_id 컬럼을 추가하고 백필.
|
||||
* 코드 변경 전이므로 기존 시스템 동작에 영향 없음.
|
||||
*
|
||||
* 대상 테이블:
|
||||
* 1. departments - is_production 플래그 추가
|
||||
* 2. sso_users - department_id 추가 (없는 경우)
|
||||
* 3. workers - user_id 추가 + 매핑 백필
|
||||
* 4~15. 12개 참조 테이블 - user_id 추가 + 백필
|
||||
*
|
||||
* @since 2026-03-05
|
||||
*/
|
||||
|
||||
exports.up = async function(knex) {
|
||||
// ============================================================
|
||||
// 1. departments 테이블에 is_production 플래그 추가
|
||||
// ============================================================
|
||||
const hasIsProduction = await knex.schema.hasColumn('departments', 'is_production');
|
||||
if (!hasIsProduction) {
|
||||
await knex.schema.table('departments', (table) => {
|
||||
table.boolean('is_production').defaultTo(false).comment('생산직 부서 여부');
|
||||
});
|
||||
await knex.raw(`UPDATE departments SET is_production = TRUE WHERE department_name LIKE '%생산%'`);
|
||||
console.log('✅ departments.is_production 추가 완료');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 2. sso_users에 department_id 추가 (없는 경우)
|
||||
// ============================================================
|
||||
const hasSsoDeptId = await knex.schema.hasColumn('sso_users', 'department_id');
|
||||
if (!hasSsoDeptId) {
|
||||
await knex.schema.table('sso_users', (table) => {
|
||||
table.integer('department_id').unsigned().defaultTo(null).comment('소속 부서 ID');
|
||||
});
|
||||
// 기존 department(문자열) → department_id(FK) 매핑
|
||||
await knex.raw(`
|
||||
UPDATE sso_users s
|
||||
INNER JOIN departments d ON s.department = d.department_name
|
||||
SET s.department_id = d.department_id
|
||||
WHERE s.department IS NOT NULL
|
||||
`);
|
||||
console.log('✅ sso_users.department_id 추가 및 백필 완료');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 3. workers 테이블에 user_id 추가 + 매핑 백필
|
||||
// ============================================================
|
||||
const hasWorkersUserId = await knex.schema.hasColumn('workers', 'user_id');
|
||||
if (!hasWorkersUserId) {
|
||||
await knex.schema.table('workers', (table) => {
|
||||
table.integer('user_id').unsigned().defaultTo(null).after('worker_id')
|
||||
.comment('sso_users.user_id 매핑');
|
||||
});
|
||||
// users 테이블을 경유하여 sso_users.user_id 매핑
|
||||
await knex.raw(`
|
||||
UPDATE workers w
|
||||
INNER JOIN users u ON u.worker_id = w.worker_id
|
||||
INNER JOIN sso_users s ON s.username = u.username
|
||||
SET w.user_id = s.user_id
|
||||
`);
|
||||
// user_id에 인덱스 추가
|
||||
await knex.raw(`ALTER TABLE workers ADD INDEX idx_workers_user_id (user_id)`);
|
||||
console.log('✅ workers.user_id 추가 및 백필 완료');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 4~15. 12개 참조 테이블에 user_id 컬럼 추가 + 백필
|
||||
// ============================================================
|
||||
|
||||
// worker_id 컬럼을 가진 테이블들
|
||||
const tablesWithWorkerId = [
|
||||
'tbm_team_assignments',
|
||||
'tbm_transfers',
|
||||
'daily_work_reports',
|
||||
'daily_attendance_records',
|
||||
'worker_vacation_balance',
|
||||
'vacation_requests',
|
||||
'vacation_balance_details',
|
||||
'worker_groups',
|
||||
'monthly_worker_status',
|
||||
];
|
||||
|
||||
for (const tableName of tablesWithWorkerId) {
|
||||
const tableExists = await knex.schema.hasTable(tableName);
|
||||
if (!tableExists) {
|
||||
console.log(`⏭️ ${tableName} 테이블이 존재하지 않음, 건너뜀`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const hasUserId = await knex.schema.hasColumn(tableName, 'user_id');
|
||||
if (!hasUserId) {
|
||||
await knex.schema.table(tableName, (table) => {
|
||||
table.integer('user_id').unsigned().defaultTo(null).comment('sso_users.user_id');
|
||||
});
|
||||
// 백필: workers 테이블의 user_id 매핑 사용
|
||||
await knex.raw(`
|
||||
UPDATE ${tableName} t
|
||||
INNER JOIN workers w ON t.worker_id = w.worker_id
|
||||
SET t.user_id = w.user_id
|
||||
WHERE w.user_id IS NOT NULL
|
||||
`);
|
||||
// 인덱스 추가
|
||||
await knex.raw(`ALTER TABLE ${tableName} ADD INDEX idx_${tableName}_user_id (user_id)`);
|
||||
console.log(`✅ ${tableName}.user_id 추가 및 백필 완료`);
|
||||
}
|
||||
}
|
||||
|
||||
// DailyIssueReports (대소문자 다른 테이블명)
|
||||
const hasDIR = await knex.schema.hasTable('DailyIssueReports');
|
||||
if (hasDIR) {
|
||||
const hasDIRUserId = await knex.schema.hasColumn('DailyIssueReports', 'user_id');
|
||||
if (!hasDIRUserId) {
|
||||
await knex.schema.table('DailyIssueReports', (table) => {
|
||||
table.integer('user_id').unsigned().defaultTo(null).comment('sso_users.user_id');
|
||||
});
|
||||
await knex.raw(`
|
||||
UPDATE DailyIssueReports t
|
||||
INNER JOIN workers w ON t.worker_id = w.worker_id
|
||||
SET t.user_id = w.user_id
|
||||
WHERE w.user_id IS NOT NULL
|
||||
`);
|
||||
console.log('✅ DailyIssueReports.user_id 추가 및 백필 완료');
|
||||
}
|
||||
}
|
||||
|
||||
// WorkReports (대소문자 다른 테이블명)
|
||||
const hasWR = await knex.schema.hasTable('WorkReports');
|
||||
if (hasWR) {
|
||||
const hasWRUserId = await knex.schema.hasColumn('WorkReports', 'user_id');
|
||||
if (!hasWRUserId) {
|
||||
await knex.schema.table('WorkReports', (table) => {
|
||||
table.integer('user_id').unsigned().defaultTo(null).comment('sso_users.user_id');
|
||||
});
|
||||
await knex.raw(`
|
||||
UPDATE WorkReports t
|
||||
INNER JOIN workers w ON t.worker_id = w.worker_id
|
||||
SET t.user_id = w.user_id
|
||||
WHERE w.user_id IS NOT NULL
|
||||
`);
|
||||
console.log('✅ WorkReports.user_id 추가 및 백필 완료');
|
||||
}
|
||||
}
|
||||
|
||||
// tbm_sessions: leader_id → leader_user_id 추가
|
||||
const hasLeaderUserId = await knex.schema.hasColumn('tbm_sessions', 'leader_user_id');
|
||||
if (!hasLeaderUserId) {
|
||||
await knex.schema.table('tbm_sessions', (table) => {
|
||||
table.integer('leader_user_id').unsigned().defaultTo(null).comment('조장 sso_users.user_id');
|
||||
});
|
||||
await knex.raw(`
|
||||
UPDATE tbm_sessions t
|
||||
INNER JOIN workers w ON t.leader_id = w.worker_id
|
||||
SET t.leader_user_id = w.user_id
|
||||
WHERE w.user_id IS NOT NULL
|
||||
`);
|
||||
await knex.raw(`ALTER TABLE tbm_sessions ADD INDEX idx_tbm_sessions_leader_user_id (leader_user_id)`);
|
||||
console.log('✅ tbm_sessions.leader_user_id 추가 및 백필 완료');
|
||||
}
|
||||
|
||||
// team_handovers: from/to_leader_id → from/to_leader_user_id 추가
|
||||
const hasFromLeaderUserId = await knex.schema.hasColumn('team_handovers', 'from_leader_user_id');
|
||||
if (!hasFromLeaderUserId) {
|
||||
await knex.schema.table('team_handovers', (table) => {
|
||||
table.integer('from_leader_user_id').unsigned().defaultTo(null).comment('인계자 sso_users.user_id');
|
||||
table.integer('to_leader_user_id').unsigned().defaultTo(null).comment('인수자 sso_users.user_id');
|
||||
});
|
||||
await knex.raw(`
|
||||
UPDATE team_handovers t
|
||||
INNER JOIN workers w1 ON t.from_leader_id = w1.worker_id
|
||||
SET t.from_leader_user_id = w1.user_id
|
||||
WHERE w1.user_id IS NOT NULL
|
||||
`);
|
||||
await knex.raw(`
|
||||
UPDATE team_handovers t
|
||||
INNER JOIN workers w2 ON t.to_leader_id = w2.worker_id
|
||||
SET t.to_leader_user_id = w2.user_id
|
||||
WHERE w2.user_id IS NOT NULL
|
||||
`);
|
||||
console.log('✅ team_handovers.from/to_leader_user_id 추가 및 백필 완료');
|
||||
}
|
||||
|
||||
console.log('🎉 Phase 1 마이그레이션 완료: 모든 테이블에 user_id 컬럼 추가 및 백필 완료');
|
||||
};
|
||||
|
||||
exports.down = async function(knex) {
|
||||
// user_id 컬럼 제거 (롤백)
|
||||
const columnsToRemove = [
|
||||
['departments', 'is_production'],
|
||||
['workers', 'user_id'],
|
||||
['tbm_team_assignments', 'user_id'],
|
||||
['tbm_transfers', 'user_id'],
|
||||
['daily_work_reports', 'user_id'],
|
||||
['daily_attendance_records', 'user_id'],
|
||||
['worker_vacation_balance', 'user_id'],
|
||||
['vacation_requests', 'user_id'],
|
||||
['vacation_balance_details', 'user_id'],
|
||||
['worker_groups', 'user_id'],
|
||||
['monthly_worker_status', 'user_id'],
|
||||
['tbm_sessions', 'leader_user_id'],
|
||||
['team_handovers', 'from_leader_user_id'],
|
||||
['team_handovers', 'to_leader_user_id'],
|
||||
];
|
||||
|
||||
for (const [tableName, columnName] of columnsToRemove) {
|
||||
const tableExists = await knex.schema.hasTable(tableName);
|
||||
if (!tableExists) continue;
|
||||
const hasColumn = await knex.schema.hasColumn(tableName, columnName);
|
||||
if (hasColumn) {
|
||||
await knex.schema.table(tableName, (table) => {
|
||||
table.dropColumn(columnName);
|
||||
});
|
||||
console.log(`↩️ ${tableName}.${columnName} 제거`);
|
||||
}
|
||||
}
|
||||
|
||||
// 대소문자 다른 테이블
|
||||
for (const tableName of ['DailyIssueReports', 'WorkReports']) {
|
||||
const tableExists = await knex.schema.hasTable(tableName);
|
||||
if (tableExists) {
|
||||
const hasColumn = await knex.schema.hasColumn(tableName, 'user_id');
|
||||
if (hasColumn) {
|
||||
await knex.schema.table(tableName, (table) => {
|
||||
table.dropColumn('user_id');
|
||||
});
|
||||
console.log(`↩️ ${tableName}.user_id 제거`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sso_users.department_id는 유지 (다른 마이그레이션에서 관리)
|
||||
};
|
||||
Reference in New Issue
Block a user