- 페이지 폴더 재구성: 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>
142 lines
11 KiB
JavaScript
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');
|
|
});
|
|
});
|
|
};
|