- 일일순회점검 시스템 신규 구현 - DB 테이블: patrol_checklist_items, daily_patrol_sessions, patrol_check_records, workplace_items, item_types - API: /api/patrol/* 엔드포인트 - 프론트엔드: 지도 기반 작업장 점검 UI - 설비 관리 기능 개선 - 구매 관련 필드 추가 (구매일, 가격, 공급업체 등) - 설비 코드 자동 생성 (TKP-XXX 형식) - 작업장 관리 개선 - 레이아웃 이미지 업로드 기능 - 마커 위치 저장 기능 - 부서 관리 기능 추가 - 사이드바 네비게이션 카테고리 재구성 - 이미지 401 오류 수정 (정적 파일 경로 처리) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
167 lines
9.8 KiB
JavaScript
167 lines
9.8 KiB
JavaScript
/**
|
|
* 마이그레이션: 일일순회점검 시스템
|
|
* 작성일: 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('✅ 모든 일일순회점검 시스템 테이블 제거 완료');
|
|
};
|