- 순찰/점검 기능 개선 (zone-detail 페이지 추가) - 출근/근태 시스템 개선 (연차 조회, 근무현황) - 작업분석 대분류 그룹화 및 마이그레이션 스크립트 - 모바일 네비게이션 UI 추가 - NAS 배포 도구 및 문서 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
113 lines
5.5 KiB
JavaScript
113 lines
5.5 KiB
JavaScript
/**
|
|
* 마이그레이션: 출근/근태 관련 테이블 생성
|
|
* 작성일: 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('✅ 모든 출근/근태 관련 테이블 제거 완료');
|
|
};
|