/** * 마이그레이션: 일일순회점검 시스템 * 작성일: 2026-02-04 * * 생성 테이블: * - patrol_checklist_items: 순회점검 체크리스트 마스터 * - daily_patrol_sessions: 순회점검 세션 기록 * - patrol_check_records: 순회점검 체크 결과 * - workplace_items: 작업장 물품 현황 (용기, 플레이트 등) */ exports.up = async function(knex) { console.log('⏳ 일일순회점검 시스템 테이블 생성 중...'); // 1. 순회점검 체크리스트 마스터 테이블 await knex.schema.createTable('patrol_checklist_items', (table) => { table.increments('item_id').primary(); table.integer('workplace_id').unsigned().nullable().comment('특정 작업장 전용 (NULL=공통)'); table.integer('category_id').unsigned().nullable().comment('특정 공장 전용 (NULL=공통)'); table.string('check_category', 50).notNullable().comment('분류 (안전, 정리정돈, 설비 등)'); table.string('check_item', 200).notNullable().comment('점검 항목'); table.text('description').nullable().comment('설명'); table.integer('display_order').defaultTo(0).comment('표시 순서'); table.boolean('is_required').defaultTo(true).comment('필수 체크 여부'); table.boolean('is_active').defaultTo(true).comment('활성 여부'); table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); table.index('workplace_id'); table.index('category_id'); table.index('check_category'); table.foreign('workplace_id').references('workplaces.workplace_id').onDelete('CASCADE'); table.foreign('category_id').references('workplace_categories.category_id').onDelete('CASCADE'); }); console.log('✅ patrol_checklist_items 테이블 생성 완료'); // 초기 순회점검 체크리스트 데이터 await knex('patrol_checklist_items').insert([ // 안전 관련 { check_category: 'SAFETY', check_item: '소화기 상태 확인', display_order: 1, is_required: true }, { check_category: 'SAFETY', check_item: '비상구 통로 확보 확인', display_order: 2, is_required: true }, { check_category: 'SAFETY', check_item: '안전표지판 부착 상태', display_order: 3, is_required: true }, { check_category: 'SAFETY', check_item: '위험물 관리 상태', display_order: 4, is_required: true }, // 정리정돈 { check_category: 'ORGANIZATION', check_item: '작업장 정리정돈 상태', display_order: 10, is_required: true }, { check_category: 'ORGANIZATION', check_item: '통로 장애물 여부', display_order: 11, is_required: true }, { check_category: 'ORGANIZATION', check_item: '폐기물 처리 상태', display_order: 12, is_required: true }, { check_category: 'ORGANIZATION', check_item: '자재 적재 상태', display_order: 13, is_required: true }, // 설비 { check_category: 'EQUIPMENT', check_item: '설비 외관 이상 여부', display_order: 20, is_required: false }, { check_category: 'EQUIPMENT', check_item: '설비 작동 상태', display_order: 21, is_required: false }, { check_category: 'EQUIPMENT', check_item: '설비 청결 상태', display_order: 22, is_required: false }, // 환경 { check_category: 'ENVIRONMENT', check_item: '조명 상태', display_order: 30, is_required: true }, { check_category: 'ENVIRONMENT', check_item: '환기 상태', display_order: 31, is_required: true }, { check_category: 'ENVIRONMENT', check_item: '누수/누유 여부', display_order: 32, is_required: true }, ]); console.log('✅ patrol_checklist_items 초기 데이터 입력 완료'); // 2. 순회점검 세션 테이블 await knex.schema.createTable('daily_patrol_sessions', (table) => { table.increments('session_id').primary(); table.date('patrol_date').notNullable().comment('점검 날짜'); table.enum('patrol_time', ['morning', 'afternoon']).notNullable().comment('점검 시간대'); table.integer('inspector_id').notNullable().comment('순찰자 user_id'); // signed (users.user_id) table.integer('category_id').unsigned().nullable().comment('공장 ID'); table.enum('status', ['in_progress', 'completed']).defaultTo('in_progress').comment('상태'); table.text('notes').nullable().comment('특이사항'); table.time('started_at').nullable().comment('점검 시작 시간'); table.time('completed_at').nullable().comment('점검 완료 시간'); table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); table.unique(['patrol_date', 'patrol_time', 'category_id']); table.index(['patrol_date', 'patrol_time']); table.index('inspector_id'); table.foreign('inspector_id').references('users.user_id'); table.foreign('category_id').references('workplace_categories.category_id').onDelete('SET NULL'); }); console.log('✅ daily_patrol_sessions 테이블 생성 완료'); // 3. 순회점검 체크 기록 테이블 await knex.schema.createTable('patrol_check_records', (table) => { table.increments('record_id').primary(); table.integer('session_id').unsigned().notNullable().comment('순회점검 세션 ID'); table.integer('workplace_id').unsigned().notNullable().comment('작업장 ID'); table.integer('check_item_id').unsigned().notNullable().comment('체크항목 ID'); table.boolean('is_checked').defaultTo(false).comment('체크 여부'); table.enum('check_result', ['good', 'warning', 'bad']).nullable().comment('점검 결과'); table.text('note').nullable().comment('비고'); table.timestamp('checked_at').nullable().comment('체크 시간'); // 인덱스명 길이 제한으로 인해 수동으로 지정 table.unique(['session_id', 'workplace_id', 'check_item_id'], 'pcr_session_wp_item_unique'); table.index(['session_id', 'workplace_id'], 'pcr_session_wp_idx'); table.foreign('session_id').references('daily_patrol_sessions.session_id').onDelete('CASCADE'); table.foreign('workplace_id').references('workplaces.workplace_id').onDelete('CASCADE'); table.foreign('check_item_id').references('patrol_checklist_items.item_id').onDelete('CASCADE'); }); console.log('✅ patrol_check_records 테이블 생성 완료'); // 4. 작업장 물품 현황 테이블 await knex.schema.createTable('workplace_items', (table) => { table.increments('item_id').primary(); table.integer('workplace_id').unsigned().notNullable().comment('작업장 ID'); table.integer('patrol_session_id').unsigned().nullable().comment('등록한 순회점검 세션'); table.integer('project_id').nullable().comment('관련 프로젝트'); // signed (projects.project_id) table.enum('item_type', ['container', 'plate', 'material', 'tool', 'other']).notNullable().comment('물품 유형'); table.string('item_name', 100).nullable().comment('물품명/설명'); table.integer('quantity').defaultTo(1).comment('수량'); table.decimal('x_percent', 5, 2).nullable().comment('지도상 X 위치 (%)'); table.decimal('y_percent', 5, 2).nullable().comment('지도상 Y 위치 (%)'); table.decimal('width_percent', 5, 2).nullable().comment('지도상 너비 (%)'); table.decimal('height_percent', 5, 2).nullable().comment('지도상 높이 (%)'); table.boolean('is_active').defaultTo(true).comment('현재 존재 여부'); table.integer('created_by').notNullable().comment('등록자 user_id'); // signed (users.user_id) table.timestamp('created_at').defaultTo(knex.fn.now()); table.integer('updated_by').nullable().comment('최종 수정자 user_id'); // signed (users.user_id) table.timestamp('updated_at').defaultTo(knex.fn.now()); table.index(['workplace_id', 'is_active']); table.index('project_id'); table.foreign('workplace_id').references('workplaces.workplace_id').onDelete('CASCADE'); table.foreign('patrol_session_id').references('daily_patrol_sessions.session_id').onDelete('SET NULL'); table.foreign('project_id').references('projects.project_id').onDelete('SET NULL'); table.foreign('created_by').references('users.user_id'); table.foreign('updated_by').references('users.user_id'); }); console.log('✅ workplace_items 테이블 생성 완료'); // 물품 유형 코드 테이블 (선택적 확장용) await knex.schema.createTable('item_types', (table) => { table.string('type_code', 20).primary(); table.string('type_name', 50).notNullable().comment('유형명'); table.string('icon', 10).nullable().comment('아이콘 이모지'); table.string('color', 20).nullable().comment('표시 색상'); table.integer('display_order').defaultTo(0); table.boolean('is_active').defaultTo(true); }); await knex('item_types').insert([ { type_code: 'container', type_name: '용기', icon: '📦', color: '#3b82f6', display_order: 1 }, { type_code: 'plate', type_name: '플레이트', icon: '🔲', color: '#10b981', display_order: 2 }, { type_code: 'material', type_name: '자재', icon: '🧱', color: '#f59e0b', display_order: 3 }, { type_code: 'tool', type_name: '공구/장비', icon: '🔧', color: '#8b5cf6', display_order: 4 }, { type_code: 'other', type_name: '기타', icon: '📍', color: '#6b7280', display_order: 5 }, ]); console.log('✅ item_types 테이블 생성 및 초기 데이터 완료'); console.log('✅ 모든 일일순회점검 시스템 테이블 생성 완료'); }; exports.down = async function(knex) { console.log('⏳ 일일순회점검 시스템 테이블 제거 중...'); await knex.schema.dropTableIfExists('item_types'); await knex.schema.dropTableIfExists('workplace_items'); await knex.schema.dropTableIfExists('patrol_check_records'); await knex.schema.dropTableIfExists('daily_patrol_sessions'); await knex.schema.dropTableIfExists('patrol_checklist_items'); console.log('✅ 모든 일일순회점검 시스템 테이블 제거 완료'); };