feat: 다수 기능 개선 - 순찰, 출근, 작업분석, 모바일 UI 등
- 순찰/점검 기능 개선 (zone-detail 페이지 추가) - 출근/근태 시스템 개선 (연차 조회, 근무현황) - 작업분석 대분류 그룹화 및 마이그레이션 스크립트 - 모바일 네비게이션 UI 추가 - NAS 배포 도구 및 문서 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
105
api.hyungi.net/db/migrations/20260205_fix_work_type_id_data.js
Normal file
105
api.hyungi.net/db/migrations/20260205_fix_work_type_id_data.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 마이그레이션: TBM 기반 작업보고서의 work_type_id를 task_id로 수정
|
||||
*
|
||||
* 문제: TBM에서 작업보고서 생성 시 work_type_id(공정 ID)가 저장됨
|
||||
* 해결: tbm_team_assignments 테이블의 task_id로 업데이트
|
||||
*
|
||||
* 실행: node db/migrations/20260205_fix_work_type_id_data.js
|
||||
*/
|
||||
|
||||
const { getDb } = require('../../dbPool');
|
||||
|
||||
async function migrate() {
|
||||
const db = await getDb();
|
||||
|
||||
console.log('🔄 TBM 기반 작업보고서 work_type_id 수정 시작...\n');
|
||||
|
||||
try {
|
||||
// 1. 수정 대상 확인 (TBM 기반이면서 work_type_id가 task_id와 다른 경우)
|
||||
const [checkResult] = await db.query(`
|
||||
SELECT
|
||||
dwr.id,
|
||||
dwr.work_type_id as current_work_type_id,
|
||||
ta.task_id as correct_task_id,
|
||||
ta.work_type_id as tbm_work_type_id,
|
||||
w.worker_name,
|
||||
dwr.report_date
|
||||
FROM daily_work_reports dwr
|
||||
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
|
||||
INNER JOIN workers w ON dwr.worker_id = w.worker_id
|
||||
WHERE dwr.tbm_assignment_id IS NOT NULL
|
||||
AND ta.task_id IS NOT NULL
|
||||
AND dwr.work_type_id != ta.task_id
|
||||
ORDER BY dwr.report_date DESC
|
||||
`);
|
||||
|
||||
console.log(`📊 수정 대상: ${checkResult.length}개 레코드\n`);
|
||||
|
||||
if (checkResult.length === 0) {
|
||||
console.log('✅ 수정할 데이터가 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 수정 대상 샘플 출력
|
||||
console.log('📋 수정 대상 샘플 (최대 10개):');
|
||||
console.log('─'.repeat(80));
|
||||
checkResult.slice(0, 10).forEach(row => {
|
||||
console.log(` ID: ${row.id} | ${row.worker_name} | ${row.report_date}`);
|
||||
console.log(` 현재 work_type_id: ${row.current_work_type_id} → 올바른 task_id: ${row.correct_task_id}`);
|
||||
});
|
||||
if (checkResult.length > 10) {
|
||||
console.log(` ... 외 ${checkResult.length - 10}개`);
|
||||
}
|
||||
console.log('─'.repeat(80));
|
||||
|
||||
// 2. 업데이트 실행
|
||||
const [updateResult] = await db.query(`
|
||||
UPDATE daily_work_reports dwr
|
||||
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
|
||||
SET dwr.work_type_id = ta.task_id
|
||||
WHERE dwr.tbm_assignment_id IS NOT NULL
|
||||
AND ta.task_id IS NOT NULL
|
||||
AND dwr.work_type_id != ta.task_id
|
||||
`);
|
||||
|
||||
console.log(`\n✅ 업데이트 완료: ${updateResult.affectedRows}개 레코드 수정됨`);
|
||||
|
||||
// 3. 수정 결과 확인
|
||||
const [verifyResult] = await db.query(`
|
||||
SELECT
|
||||
dwr.id,
|
||||
dwr.work_type_id,
|
||||
ta.task_id,
|
||||
t.task_name,
|
||||
wt.name as work_type_name
|
||||
FROM daily_work_reports dwr
|
||||
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
|
||||
LEFT JOIN tasks t ON dwr.work_type_id = t.task_id
|
||||
LEFT JOIN work_types wt ON t.work_type_id = wt.id
|
||||
WHERE dwr.tbm_assignment_id IS NOT NULL
|
||||
LIMIT 5
|
||||
`);
|
||||
|
||||
console.log('\n📋 수정 후 샘플 확인:');
|
||||
console.log('─'.repeat(80));
|
||||
verifyResult.forEach(row => {
|
||||
console.log(` ID: ${row.id} | work_type_id: ${row.work_type_id} | task: ${row.task_name || 'N/A'} | 공정: ${row.work_type_name || 'N/A'}`);
|
||||
});
|
||||
console.log('─'.repeat(80));
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 마이그레이션 실패:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 실행
|
||||
migrate()
|
||||
.then(() => {
|
||||
console.log('\n🎉 마이그레이션 완료!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('\n💥 마이그레이션 실패:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
-- ============================================
|
||||
-- error_types → issue_report_items 마이그레이션
|
||||
-- 실행 전 반드시 백업하세요!
|
||||
-- ============================================
|
||||
|
||||
-- STEP 1: 현재 상태 확인
|
||||
-- ============================================
|
||||
SELECT 'Before Migration' as status;
|
||||
SELECT error_type_id, COUNT(*) as cnt FROM daily_work_reports WHERE error_type_id IS NOT NULL GROUP BY error_type_id;
|
||||
|
||||
|
||||
-- STEP 2: 매핑 업데이트 실행
|
||||
-- ============================================
|
||||
-- 주의: 순서가 중요! (충돌 방지를 위해 큰 숫자부터)
|
||||
|
||||
-- 6 (검사불량) → 14 (치수 검사 누락)
|
||||
UPDATE daily_work_reports SET error_type_id = 14 WHERE error_type_id = 6;
|
||||
|
||||
-- 5 (설비고장) → 38 (기계 고장)
|
||||
UPDATE daily_work_reports SET error_type_id = 38 WHERE error_type_id = 5;
|
||||
|
||||
-- 4 (작업불량) → 43 (NDE 불합격)
|
||||
UPDATE daily_work_reports SET error_type_id = 43 WHERE error_type_id = 4;
|
||||
|
||||
-- 3 (입고지연) → 1 (배관 자재 미입고) - 이미 1이므로 충돌 가능, 임시값 사용
|
||||
UPDATE daily_work_reports SET error_type_id = 99991 WHERE error_type_id = 3;
|
||||
|
||||
-- 2 (외주작업 불량) → 10 (외관 불량)
|
||||
UPDATE daily_work_reports SET error_type_id = 10 WHERE error_type_id = 2;
|
||||
|
||||
-- 1 (설계미스) → 6 (도면 치수 오류) - 6은 이미 업데이트됨
|
||||
UPDATE daily_work_reports SET error_type_id = 6 WHERE error_type_id = 1;
|
||||
|
||||
-- 임시값 복원: 99991 → 1
|
||||
UPDATE daily_work_reports SET error_type_id = 1 WHERE error_type_id = 99991;
|
||||
|
||||
|
||||
-- STEP 3: 마이그레이션 결과 확인
|
||||
-- ============================================
|
||||
SELECT 'After Migration' as status;
|
||||
SELECT
|
||||
dwr.error_type_id,
|
||||
iri.item_name,
|
||||
irc.category_name,
|
||||
COUNT(*) as cnt
|
||||
FROM daily_work_reports dwr
|
||||
LEFT JOIN issue_report_items iri ON dwr.error_type_id = iri.item_id
|
||||
LEFT JOIN issue_report_categories irc ON iri.category_id = irc.category_id
|
||||
WHERE dwr.error_type_id IS NOT NULL
|
||||
GROUP BY dwr.error_type_id, iri.item_name, irc.category_name;
|
||||
|
||||
|
||||
-- STEP 4: error_types 테이블 삭제 (선택사항)
|
||||
-- ============================================
|
||||
-- 마이그레이션 확인 후 주석 해제하여 실행
|
||||
-- DROP TABLE IF EXISTS error_types;
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* 마이그레이션: error_types에서 issue_report_items로 전환
|
||||
*
|
||||
* 기존 daily_work_reports.error_type_id가 error_types.id를 참조하던 것을
|
||||
* issue_report_items.item_id를 참조하도록 변경
|
||||
*
|
||||
* 기존 error_types 데이터:
|
||||
* id=1: 설계미스
|
||||
* id=2: 외주작업 불량
|
||||
* id=3: 입고지연
|
||||
*
|
||||
* 새 issue_report_categories 데이터:
|
||||
* category_id=1: 자재누락 (nonconformity)
|
||||
* category_id=2: 설계미스 (nonconformity)
|
||||
* category_id=3: 입고불량 (nonconformity)
|
||||
*
|
||||
* 매핑 전략:
|
||||
* - error_types.id=1 (설계미스) → issue_report_items에서 '설계미스' 카테고리의 첫 번째 항목
|
||||
* - error_types.id=2 (외주작업 불량) → issue_report_items에서 '입고불량' 카테고리의 '외관 불량' 항목
|
||||
* - error_types.id=3 (입고지연) → issue_report_items에서 '자재누락' 카테고리의 첫 번째 항목
|
||||
*/
|
||||
|
||||
exports.up = async function(knex) {
|
||||
console.log('=== error_type_id 마이그레이션 시작 ===');
|
||||
|
||||
// 1. 기존 error_types 데이터와 새 issue_report_items 매핑 테이블 조회
|
||||
const [categories] = await knex.raw(`
|
||||
SELECT category_id, category_name
|
||||
FROM issue_report_categories
|
||||
WHERE category_type = 'nonconformity'
|
||||
`);
|
||||
console.log('부적합 카테고리:', categories);
|
||||
|
||||
const [items] = await knex.raw(`
|
||||
SELECT iri.item_id, iri.item_name, iri.category_id, irc.category_name
|
||||
FROM issue_report_items iri
|
||||
JOIN issue_report_categories irc ON iri.category_id = irc.category_id
|
||||
WHERE irc.category_type = 'nonconformity'
|
||||
ORDER BY iri.category_id, iri.display_order
|
||||
`);
|
||||
console.log('부적합 항목:', items);
|
||||
|
||||
// 2. 매핑 정의 (기존 error_type_id → 새 issue_report_items.item_id)
|
||||
// 설계미스 카테고리 찾기
|
||||
const designMissCategory = categories.find(c => c.category_name === '설계미스');
|
||||
const incomingDefectCategory = categories.find(c => c.category_name === '입고불량');
|
||||
const materialShortageCategory = categories.find(c => c.category_name === '자재누락');
|
||||
|
||||
// 각 카테고리의 첫 번째 항목 찾기
|
||||
const designMissItem = items.find(i => i.category_id === designMissCategory?.category_id);
|
||||
const incomingDefectItem = items.find(i => i.category_id === incomingDefectCategory?.category_id);
|
||||
const materialShortageItem = items.find(i => i.category_id === materialShortageCategory?.category_id);
|
||||
|
||||
console.log('매핑 결과:');
|
||||
console.log(' - 설계미스(1) → item_id:', designMissItem?.item_id);
|
||||
console.log(' - 외주작업불량(2) → item_id:', incomingDefectItem?.item_id);
|
||||
console.log(' - 입고지연(3) → item_id:', materialShortageItem?.item_id);
|
||||
|
||||
// 3. 기존 데이터 업데이트
|
||||
if (designMissItem) {
|
||||
const [result1] = await knex.raw(`
|
||||
UPDATE daily_work_reports
|
||||
SET error_type_id = ?
|
||||
WHERE error_type_id = 1
|
||||
`, [designMissItem.item_id]);
|
||||
console.log('설계미스(1) 업데이트:', result1.affectedRows, '건');
|
||||
}
|
||||
|
||||
if (incomingDefectItem) {
|
||||
const [result2] = await knex.raw(`
|
||||
UPDATE daily_work_reports
|
||||
SET error_type_id = ?
|
||||
WHERE error_type_id = 2
|
||||
`, [incomingDefectItem.item_id]);
|
||||
console.log('외주작업불량(2) 업데이트:', result2.affectedRows, '건');
|
||||
}
|
||||
|
||||
if (materialShortageItem) {
|
||||
const [result3] = await knex.raw(`
|
||||
UPDATE daily_work_reports
|
||||
SET error_type_id = ?
|
||||
WHERE error_type_id = 3
|
||||
`, [materialShortageItem.item_id]);
|
||||
console.log('입고지연(3) 업데이트:', result3.affectedRows, '건');
|
||||
}
|
||||
|
||||
// 4. 매핑 안된 나머지 데이터 확인 (4 이상의 error_type_id)
|
||||
const [unmapped] = await knex.raw(`
|
||||
SELECT DISTINCT error_type_id, COUNT(*) as cnt
|
||||
FROM daily_work_reports
|
||||
WHERE error_type_id IS NOT NULL
|
||||
AND error_type_id NOT IN (?, ?, ?)
|
||||
GROUP BY error_type_id
|
||||
`, [
|
||||
designMissItem?.item_id || 0,
|
||||
incomingDefectItem?.item_id || 0,
|
||||
materialShortageItem?.item_id || 0
|
||||
]);
|
||||
|
||||
if (unmapped.length > 0) {
|
||||
console.log('⚠️ 매핑되지 않은 error_type_id 발견:', unmapped);
|
||||
console.log(' 이 데이터는 수동으로 확인 필요');
|
||||
}
|
||||
|
||||
console.log('=== error_type_id 마이그레이션 완료 ===');
|
||||
};
|
||||
|
||||
exports.down = async function(knex) {
|
||||
// 롤백은 복잡하므로 로그만 출력
|
||||
console.log('⚠️ 이 마이그레이션은 자동 롤백을 지원하지 않습니다.');
|
||||
console.log(' 데이터 복구가 필요한 경우 백업에서 복원해주세요.');
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
-- ============================================
|
||||
-- error_types → issue_report_items 마이그레이션
|
||||
-- ============================================
|
||||
|
||||
-- STEP 1: 현재 데이터 확인
|
||||
-- ============================================
|
||||
|
||||
-- 기존 error_types 확인
|
||||
SELECT * FROM error_types;
|
||||
|
||||
-- 새 issue_report_categories 확인 (부적합만)
|
||||
SELECT * FROM issue_report_categories WHERE category_type = 'nonconformity';
|
||||
|
||||
-- 새 issue_report_items 확인 (부적합만)
|
||||
SELECT
|
||||
iri.item_id,
|
||||
iri.item_name,
|
||||
iri.category_id,
|
||||
irc.category_name
|
||||
FROM issue_report_items iri
|
||||
JOIN issue_report_categories irc ON iri.category_id = irc.category_id
|
||||
WHERE irc.category_type = 'nonconformity'
|
||||
ORDER BY irc.display_order, iri.display_order;
|
||||
|
||||
-- 현재 daily_work_reports에서 사용 중인 error_type_id 확인
|
||||
SELECT
|
||||
error_type_id,
|
||||
COUNT(*) as cnt,
|
||||
et.name as old_error_name
|
||||
FROM daily_work_reports dwr
|
||||
LEFT JOIN error_types et ON dwr.error_type_id = et.id
|
||||
WHERE error_type_id IS NOT NULL
|
||||
GROUP BY error_type_id
|
||||
ORDER BY error_type_id;
|
||||
|
||||
|
||||
-- STEP 2: 매핑 업데이트 (실제 item_id 확인 후 수정 필요!)
|
||||
-- ============================================
|
||||
|
||||
-- 먼저 위 쿼리로 실제 item_id 값을 확인하세요!
|
||||
-- 아래는 예시입니다. 실제 값으로 수정해서 사용하세요.
|
||||
|
||||
-- 예시: 설계미스(error_type_id=1) → 설계미스 카테고리의 '도면 치수 오류' 항목
|
||||
-- UPDATE daily_work_reports SET error_type_id = 6 WHERE error_type_id = 1;
|
||||
|
||||
-- 예시: 외주작업 불량(error_type_id=2) → 입고불량 카테고리의 '외관 불량' 항목
|
||||
-- UPDATE daily_work_reports SET error_type_id = 10 WHERE error_type_id = 2;
|
||||
|
||||
-- 예시: 입고지연(error_type_id=3) → 자재누락 카테고리의 '배관 자재 미입고' 항목
|
||||
-- UPDATE daily_work_reports SET error_type_id = 1 WHERE error_type_id = 3;
|
||||
|
||||
|
||||
-- STEP 3: 매핑 검증
|
||||
-- ============================================
|
||||
|
||||
-- 업데이트 후 확인
|
||||
SELECT
|
||||
dwr.error_type_id,
|
||||
iri.item_name,
|
||||
irc.category_name,
|
||||
COUNT(*) as cnt
|
||||
FROM daily_work_reports dwr
|
||||
LEFT JOIN issue_report_items iri ON dwr.error_type_id = iri.item_id
|
||||
LEFT JOIN issue_report_categories irc ON iri.category_id = irc.category_id
|
||||
WHERE dwr.error_type_id IS NOT NULL
|
||||
GROUP BY dwr.error_type_id, iri.item_name, irc.category_name;
|
||||
|
||||
|
||||
-- STEP 4: error_types 테이블 삭제 (매핑 완료 후)
|
||||
-- ============================================
|
||||
|
||||
-- 주의: 반드시 STEP 2, 3 완료 후 실행!
|
||||
-- DROP TABLE IF EXISTS error_types;
|
||||
Reference in New Issue
Block a user