/** * vacation_balance_details 테이블 생성 및 데이터 마이그레이션 * - 작업자별, 휴가 유형별, 연도별 휴가 잔액 관리 * - 기존 worker_vacation_balance 데이터 이관 */ exports.up = async function(knex) { // vacation_balance_details 테이블 생성 await knex.schema.createTable('vacation_balance_details', (table) => { table.increments('id').primary(); table.integer('worker_id').notNullable().comment('작업자 ID'); table.integer('vacation_type_id').unsigned().notNullable().comment('휴가 유형 ID'); table.integer('year').notNullable().comment('연도'); table.decimal('total_days', 4, 1).defaultTo(0).comment('총 발생 일수'); table.decimal('used_days', 4, 1).defaultTo(0).comment('사용 일수'); table.text('notes').nullable().comment('비고'); table.integer('created_by').notNullable().comment('생성자 ID'); table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); // 인덱스 table.unique(['worker_id', 'vacation_type_id', 'year'], 'unique_worker_vacation_year'); table.index(['worker_id', 'year'], 'idx_worker_year'); table.index('vacation_type_id', 'idx_vacation_type'); // 외래키 table.foreign('worker_id').references('worker_id').inTable('workers').onDelete('CASCADE'); table.foreign('vacation_type_id').references('id').inTable('vacation_types').onDelete('RESTRICT'); table.foreign('created_by').references('user_id').inTable('users'); }); // remaining_days를 generated column으로 추가 (Raw SQL) await knex.raw(` ALTER TABLE vacation_balance_details ADD COLUMN remaining_days DECIMAL(4,1) GENERATED ALWAYS AS (total_days - used_days) STORED COMMENT '잔여 일수' `); console.log('✅ vacation_balance_details 테이블 생성 완료'); // 기존 worker_vacation_balance 데이터를 vacation_balance_details로 마이그레이션 const existingBalances = await knex('worker_vacation_balance').select('*'); if (existingBalances.length > 0) { // ANNUAL 휴가 유형 ID 조회 const annualType = await knex('vacation_types') .where('type_code', 'ANNUAL') .first(); if (!annualType) { throw new Error('ANNUAL 휴가 유형을 찾을 수 없습니다'); } // 관리자 사용자 ID 조회 (created_by 용) // role_id 1 = System Admin, 2 = Admin const adminUser = await knex('users') .whereIn('role_id', [1, 2]) .first(); const createdById = adminUser ? adminUser.user_id : 1; // 데이터 변환 및 삽입 const balanceDetails = existingBalances.map(balance => ({ worker_id: balance.worker_id, vacation_type_id: annualType.id, year: balance.year, total_days: balance.total_annual_leave || 0, used_days: balance.used_annual_leave || 0, notes: 'Migrated from worker_vacation_balance', created_by: createdById, created_at: balance.created_at, updated_at: balance.updated_at })); await knex('vacation_balance_details').insert(balanceDetails); console.log(`✅ ${balanceDetails.length}건의 기존 휴가 데이터 마이그레이션 완료`); } }; exports.down = async function(knex) { // vacation_balance_details 테이블 삭제 await knex.schema.dropTableIfExists('vacation_balance_details'); console.log('✅ vacation_balance_details 테이블 롤백 완료'); };