Files
TK-FB-Project/api.hyungi.net/db/migrations/20260202100000_expand_safety_checklist.js
Hyungi Ahn 74d3a78aa3 feat: 페이지 구조 재구성 및 사이드바 네비게이션 구현
- 페이지 폴더 재구성: safety/, attendance/ 폴더 신규 생성
  - work/ → safety/: 이슈 신고, 출입 신청 관련 페이지 이동
  - common/ → attendance/: 근태/휴가 관련 페이지 이동
  - admin/ 정리: safety-* 파일들을 safety/로 이동

- 사이드바 네비게이션 메뉴 구현
  - 카테고리별 메뉴: 작업관리, 안전관리, 근태관리, 시스템관리
  - 접기/펼치기 기능 및 상태 저장
  - 관리자 전용 메뉴 자동 표시/숨김

- 날씨 API 연동 (기상청 단기예보)
  - TBM 및 navbar에 현재 날씨 표시
  - weatherService.js 추가

- 안전 체크리스트 확장
  - 기본/날씨별/작업별 체크 유형 추가
  - checklist-manage.html 페이지 추가

- 이슈 신고 시스템 구현
  - workIssueController, workIssueModel, workIssueRoutes 추가

- DB 마이그레이션 파일 추가 (실행 대기)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 14:27:22 +09:00

142 lines
11 KiB
JavaScript

/**
* 안전 체크리스트 확장 마이그레이션
*
* 1. tbm_safety_checks 테이블 확장 (check_type, weather_condition, task_id)
* 2. weather_conditions 테이블 생성 (날씨 조건 코드)
* 3. tbm_weather_records 테이블 생성 (세션별 날씨 기록)
* 4. 초기 날씨별 체크항목 데이터
*
* @since 2026-02-02
*/
exports.up = function(knex) {
return knex.schema
// 1. tbm_safety_checks 테이블 확장
.alterTable('tbm_safety_checks', function(table) {
table.enum('check_type', ['basic', 'weather', 'task']).defaultTo('basic').after('check_category');
table.string('weather_condition', 50).nullable().after('check_type');
table.integer('task_id').unsigned().nullable().after('weather_condition');
// 인덱스 추가
table.index('check_type');
table.index('weather_condition');
table.index('task_id');
})
// 2. weather_conditions 테이블 생성
.createTable('weather_conditions', function(table) {
table.string('condition_code', 50).primary();
table.string('condition_name', 100).notNullable();
table.text('description').nullable();
table.string('icon', 50).nullable();
table.decimal('temp_threshold_min', 4, 1).nullable(); // 최소 기온 기준
table.decimal('temp_threshold_max', 4, 1).nullable(); // 최대 기온 기준
table.decimal('wind_threshold', 4, 1).nullable(); // 풍속 기준 (m/s)
table.decimal('precip_threshold', 5, 1).nullable(); // 강수량 기준 (mm)
table.boolean('is_active').defaultTo(true);
table.integer('display_order').defaultTo(0);
table.timestamp('created_at').defaultTo(knex.fn.now());
})
// 3. tbm_weather_records 테이블 생성
.createTable('tbm_weather_records', function(table) {
table.increments('record_id').primary();
table.integer('session_id').unsigned().notNullable();
table.date('weather_date').notNullable();
table.decimal('temperature', 4, 1).nullable(); // 기온 (섭씨)
table.integer('humidity').nullable(); // 습도 (%)
table.decimal('wind_speed', 4, 1).nullable(); // 풍속 (m/s)
table.decimal('precipitation', 5, 1).nullable(); // 강수량 (mm)
table.string('sky_condition', 50).nullable(); // 하늘 상태
table.string('weather_condition', 50).nullable(); // 주요 날씨 상태
table.json('weather_conditions').nullable(); // 복수 조건 ['rain', 'wind']
table.string('data_source', 50).defaultTo('api'); // 데이터 출처
table.timestamp('fetched_at').nullable();
table.timestamp('created_at').defaultTo(knex.fn.now());
// 외래키
table.foreign('session_id').references('session_id').inTable('tbm_sessions').onDelete('CASCADE');
// 인덱스
table.index('weather_date');
table.unique(['session_id']);
})
// 4. 초기 데이터 삽입
.then(function() {
// 기존 체크항목을 'basic' 유형으로 업데이트
return knex('tbm_safety_checks').update({ check_type: 'basic' });
})
.then(function() {
// 날씨 조건 코드 삽입
return knex('weather_conditions').insert([
{ condition_code: 'clear', condition_name: '맑음', description: '맑은 날씨', icon: 'sunny', display_order: 1 },
{ condition_code: 'rain', condition_name: '비', description: '비 오는 날씨', icon: 'rainy', precip_threshold: 0.1, display_order: 2 },
{ condition_code: 'snow', condition_name: '눈', description: '눈 오는 날씨', icon: 'snowy', display_order: 3 },
{ condition_code: 'heat', condition_name: '폭염', description: '기온 35도 이상', icon: 'hot', temp_threshold_min: 35, display_order: 4 },
{ condition_code: 'cold', condition_name: '한파', description: '기온 영하 10도 이하', icon: 'cold', temp_threshold_max: -10, display_order: 5 },
{ condition_code: 'wind', condition_name: '강풍', description: '풍속 10m/s 이상', icon: 'windy', wind_threshold: 10, display_order: 6 },
{ condition_code: 'fog', condition_name: '안개', description: '시정 1km 미만', icon: 'foggy', display_order: 7 },
{ condition_code: 'dust', condition_name: '미세먼지', description: '미세먼지 나쁨 이상', icon: 'dusty', display_order: 8 }
]);
})
.then(function() {
// 날씨별 안전 체크항목 삽입
return knex('tbm_safety_checks').insert([
// 비 (rain)
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'rain', check_item: '우의/우산 준비 확인', description: '비 오는 날 우의 또는 우산 준비 여부', is_required: true, display_order: 1 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'rain', check_item: '미끄럼 방지 조치 확인', description: '빗물로 인한 미끄러움 방지 조치', is_required: true, display_order: 2 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'rain', check_item: '전기 작업 중단 여부 확인', description: '우천 시 전기 작업 중단 필요성 확인', is_required: true, display_order: 3 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'rain', check_item: '배수 상태 확인', description: '작업장 배수 상태 점검', is_required: false, display_order: 4 },
// 눈 (snow)
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'snow', check_item: '제설 작업 완료 확인', description: '작업장 주변 제설 작업 완료 여부', is_required: true, display_order: 1 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'snow', check_item: '동파 방지 조치 확인', description: '배관 및 설비 동파 방지 조치', is_required: true, display_order: 2 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'snow', check_item: '미끄럼 방지 모래/염화칼슘 비치', description: '미끄럼 방지를 위한 모래 또는 염화칼슘 비치', is_required: true, display_order: 3 },
// 폭염 (heat)
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'heat', check_item: '그늘막/휴게소 확보', description: '무더위 휴식을 위한 그늘막 또는 휴게소 확보', is_required: true, display_order: 1 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'heat', check_item: '음료수/식염 포도당 비치', description: '열사병 예방을 위한 음료수 및 염분 보충제 비치', is_required: true, display_order: 2 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'heat', check_item: '무더위 휴식 시간 확보', description: '10~15시 사이 충분한 휴식 시간 확보', is_required: true, display_order: 3 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'heat', check_item: '작업자 건강 상태 확인', description: '열사병 증상 체크 및 건강 상태 확인', is_required: true, display_order: 4 },
// 한파 (cold)
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'cold', check_item: '방한복/방한장갑 착용 확인', description: '동상 방지를 위한 방한복 및 방한장갑 착용', is_required: true, display_order: 1 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'cold', check_item: '난방시설 가동 확인', description: '휴게 공간 난방시설 가동 상태 확인', is_required: true, display_order: 2 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'cold', check_item: '온열 음료 비치', description: '체온 유지를 위한 따뜻한 음료 비치', is_required: false, display_order: 3 },
// 강풍 (wind)
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'wind', check_item: '고소 작업 중단 여부 확인', description: '강풍 시 고소 작업 중단 필요성 확인', is_required: true, display_order: 1 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'wind', check_item: '자재/장비 결박 확인', description: '바람에 날릴 수 있는 자재 및 장비 고정', is_required: true, display_order: 2 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'wind', check_item: '가설물 안전 점검', description: '가설 구조물 및 비계 안전 상태 점검', is_required: true, display_order: 3 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'wind', check_item: '크레인 작업 중단 여부 확인', description: '강풍 시 크레인 작업 중단 필요성 확인', is_required: true, display_order: 4 },
// 안개 (fog)
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'fog', check_item: '경광등/조명 확보', description: '시정 확보를 위한 경광등 및 조명 설치', is_required: true, display_order: 1 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'fog', check_item: '차량 운행 주의 안내', description: '안개로 인한 차량 운행 주의 안내', is_required: true, display_order: 2 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'fog', check_item: '작업 구역 표시 강화', description: '시인성 확보를 위한 작업 구역 표시 강화', is_required: false, display_order: 3 },
// 미세먼지 (dust)
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'dust', check_item: '보호 마스크 착용 확인', description: 'KF94 이상 마스크 착용 여부 확인', is_required: true, display_order: 1 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'dust', check_item: '실외 작업 시간 조정', description: '미세먼지 농도에 따른 실외 작업 시간 조정', is_required: true, display_order: 2 },
{ check_category: 'WEATHER', check_type: 'weather', weather_condition: 'dust', check_item: '호흡기 질환자 실내 배치', description: '호흡기 질환 작업자 실내 작업 배치', is_required: false, display_order: 3 }
]);
});
};
exports.down = function(knex) {
return knex.schema
.dropTableIfExists('tbm_weather_records')
.dropTableIfExists('weather_conditions')
.then(function() {
return knex.schema.alterTable('tbm_safety_checks', function(table) {
table.dropIndex('check_type');
table.dropIndex('weather_condition');
table.dropIndex('task_id');
table.dropColumn('check_type');
table.dropColumn('weather_condition');
table.dropColumn('task_id');
});
});
};