/** * 마이그레이션: 출근/근태 관련 테이블 생성 * 작성일: 2026-01-19 * * 생성 테이블: * - work_attendance_types: 출근 유형 (정상, 지각, 조퇴, 결근, 휴가) * - vacation_types: 휴가 유형 (연차, 반차, 병가, 경조사) * - daily_attendance_records: 일일 출근 기록 * - worker_vacation_balance: 작업자 연차 잔액 (연도별) */ exports.up = async function(knex) { console.log('⏳ 출근/근태 관련 테이블 생성 중...'); // 1. 출근 유형 테이블 await knex.schema.createTable('work_attendance_types', (table) => { table.increments('id').primary(); table.string('type_code', 20).unique().notNullable().comment('유형 코드'); table.string('type_name', 50).notNullable().comment('유형 이름'); table.text('description').nullable().comment('설명'); table.boolean('is_active').defaultTo(true).comment('활성 여부'); table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); }); console.log('✅ work_attendance_types 테이블 생성 완료'); // 초기 데이터 입력 await knex('work_attendance_types').insert([ { type_code: 'NORMAL', type_name: '정상 출근', description: '정상 출근' }, { type_code: 'LATE', type_name: '지각', description: '지각' }, { type_code: 'EARLY_LEAVE', type_name: '조퇴', description: '조퇴' }, { type_code: 'ABSENT', type_name: '결근', description: '무단 결근' }, { type_code: 'VACATION', type_name: '휴가', description: '승인된 휴가' } ]); console.log('✅ work_attendance_types 초기 데이터 입력 완료'); // 2. 휴가 유형 테이블 await knex.schema.createTable('vacation_types', (table) => { table.increments('id').primary(); table.string('type_code', 20).unique().notNullable().comment('휴가 코드'); table.string('type_name', 50).notNullable().comment('휴가 이름'); table.decimal('deduct_days', 3, 1).defaultTo(1.0).comment('차감 일수'); table.boolean('is_active').defaultTo(true).comment('활성 여부'); table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); }); console.log('✅ vacation_types 테이블 생성 완료'); // 초기 데이터 입력 await knex('vacation_types').insert([ { type_code: 'ANNUAL', type_name: '연차', deduct_days: 1.0 }, { type_code: 'HALF_ANNUAL', type_name: '반차', deduct_days: 0.5 }, { type_code: 'SICK', type_name: '병가', deduct_days: 1.0 }, { type_code: 'SPECIAL', type_name: '경조사', deduct_days: 0 } ]); console.log('✅ vacation_types 초기 데이터 입력 완료'); // 3. 일일 출근 기록 테이블 await knex.schema.createTable('daily_attendance_records', (table) => { table.increments('id').primary(); table.integer('worker_id').unsigned().notNullable().comment('작업자 ID'); table.date('record_date').notNullable().comment('기록 날짜'); table.integer('attendance_type_id').unsigned().notNullable().comment('출근 유형 ID'); table.integer('vacation_type_id').unsigned().nullable().comment('휴가 유형 ID'); table.time('check_in_time').nullable().comment('출근 시간'); table.time('check_out_time').nullable().comment('퇴근 시간'); table.decimal('total_work_hours', 4, 2).defaultTo(0).comment('총 근무 시간'); table.boolean('is_overtime_approved').defaultTo(false).comment('초과근무 승인 여부'); table.text('notes').nullable().comment('비고'); table.integer('created_by').unsigned().notNullable().comment('등록자 user_id'); table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); // 인덱스 및 제약조건 table.unique(['worker_id', 'record_date']); table.foreign('worker_id').references('workers.worker_id').onDelete('CASCADE'); table.foreign('attendance_type_id').references('work_attendance_types.id'); table.foreign('vacation_type_id').references('vacation_types.id'); table.foreign('created_by').references('users.user_id'); }); console.log('✅ daily_attendance_records 테이블 생성 완료'); // 4. 작업자 연차 잔액 테이블 await knex.schema.createTable('worker_vacation_balance', (table) => { table.increments('id').primary(); table.integer('worker_id').unsigned().notNullable().comment('작업자 ID'); table.integer('year').notNullable().comment('연도'); table.decimal('total_annual_leave', 4, 1).defaultTo(15.0).comment('총 연차'); table.decimal('used_annual_leave', 4, 1).defaultTo(0).comment('사용 연차'); // remaining_annual_leave는 애플리케이션 레벨에서 계산 table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); // 인덱스 및 제약조건 table.unique(['worker_id', 'year']); table.foreign('worker_id').references('workers.worker_id').onDelete('CASCADE'); }); console.log('✅ worker_vacation_balance 테이블 생성 완료'); console.log('✅ 모든 출근/근태 관련 테이블 생성 완료'); }; exports.down = async function(knex) { console.log('⏳ 출근/근태 관련 테이블 제거 중...'); await knex.schema.dropTableIfExists('worker_vacation_balance'); await knex.schema.dropTableIfExists('daily_attendance_records'); await knex.schema.dropTableIfExists('vacation_types'); await knex.schema.dropTableIfExists('work_attendance_types'); console.log('✅ 모든 출근/근태 관련 테이블 제거 완료'); };