Files
tk-factory-services/system1-factory/api/models/tbmTransferModel.js
Hyungi Ahn abd7564e6b refactor: worker_id → user_id 전체 마이그레이션 (Phase 1-4)
sso_users.user_id를 단일 식별자로 통합. JWT에서 worker_id 제거,
department_id/is_production 추가. 백엔드 15개 모델, 11개 컨트롤러,
4개 서비스, 7개 라우트, 프론트엔드 32+ JS/11+ HTML 변환.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:13:10 +09:00

272 lines
9.0 KiB
JavaScript

// models/tbmTransferModel.js - TBM 작업자 이동 모델
const { getDb } = require('../dbPool');
const TbmTransferModel = {
/**
* 작업자 이동 실행 (보내기/빼오기)
* 트랜잭션: source work_hours 업데이트 + dest INSERT + 로그 INSERT
*/
async createTransfer(transferData) {
const db = await getDb();
const conn = await db.getConnection();
try {
await conn.beginTransaction();
const {
transfer_type, user_id, source_session_id, dest_session_id,
hours, initiated_by, transfer_date,
project_id, work_type_id, task_id, workplace_category_id, workplace_id
} = transferData;
// 1. source 세션에서 해당 작업자의 work_hours 업데이트
const [sourceRows] = await conn.query(
'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?',
[source_session_id, user_id]
);
if (sourceRows.length === 0) {
await conn.rollback();
return { success: false, message: '원본 세션에서 해당 작업자를 찾을 수 없습니다.' };
}
const currentHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours);
const newSourceHours = currentHours - parseFloat(hours);
if (newSourceHours < 0) {
await conn.rollback();
return { success: false, message: '이동 시간이 현재 배정 시간보다 큽니다.' };
}
await conn.query(
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND user_id = ?',
[newSourceHours, source_session_id, user_id]
);
// 2. dest 세션에 작업자 INSERT (이미 있으면 work_hours 증가)
const [destRows] = await conn.query(
'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?',
[dest_session_id, user_id]
);
if (destRows.length > 0) {
const existingHours = destRows[0].work_hours === null ? 8 : parseFloat(destRows[0].work_hours);
await conn.query(
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND user_id = ?',
[existingHours + parseFloat(hours), dest_session_id, user_id]
);
} else {
await conn.query(
`INSERT INTO tbm_team_assignments
(session_id, user_id, work_hours, project_id, work_type_id, task_id, workplace_category_id, workplace_id, is_present)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)`,
[dest_session_id, user_id, parseFloat(hours),
project_id || null, work_type_id || null, task_id || null,
workplace_category_id || null, workplace_id || null]
);
}
// 3. tbm_transfers에 로그 INSERT
const [logResult] = await conn.query(
`INSERT INTO tbm_transfers
(transfer_date, user_id, source_session_id, dest_session_id, hours, transfer_type, initiated_by)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[transfer_date, user_id, source_session_id, dest_session_id, parseFloat(hours), transfer_type, initiated_by]
);
// 4. 합계 시간 검증 (경고만, 차단 안함)
const [totalRows] = await conn.query(
`SELECT SUM(COALESCE(work_hours, 8)) as total_hours
FROM tbm_team_assignments
WHERE user_id = ?
AND session_id IN (SELECT session_id FROM tbm_sessions WHERE session_date = ?)`,
[user_id, transfer_date]
);
const totalHours = totalRows[0] ? parseFloat(totalRows[0].total_hours) : 0;
await conn.commit();
const result = {
success: true,
transfer_id: logResult.insertId,
total_hours: totalHours
};
if (totalHours > 8) {
result.warning = `해당 작업자의 당일 합계가 ${totalHours}h입니다 (8h 초과).`;
}
return result;
} catch (err) {
try { await conn.rollback(); } catch (e) {}
throw err;
} finally {
conn.release();
}
},
/**
* 이동 취소 (원복)
*/
async cancelTransfer(transferId) {
const db = await getDb();
const conn = await db.getConnection();
try {
await conn.beginTransaction();
// 1. 이동 로그 조회
const [transfers] = await conn.query(
'SELECT * FROM tbm_transfers WHERE transfer_id = ?',
[transferId]
);
if (transfers.length === 0) {
await conn.rollback();
return { success: false, message: '이동 기록을 찾을 수 없습니다.' };
}
const t = transfers[0];
// 2. dest 세션에서 작업자 work_hours 감소 (또는 삭제)
const [destRows] = await conn.query(
'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?',
[t.dest_session_id, t.user_id]
);
if (destRows.length > 0) {
const destHours = destRows[0].work_hours === null ? 8 : parseFloat(destRows[0].work_hours);
const newDestHours = destHours - parseFloat(t.hours);
if (newDestHours <= 0) {
await conn.query(
'DELETE FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?',
[t.dest_session_id, t.user_id]
);
} else {
await conn.query(
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND user_id = ?',
[newDestHours, t.dest_session_id, t.user_id]
);
}
}
// 3. source 세션에서 작업자 work_hours 복원
const [sourceRows] = await conn.query(
'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?',
[t.source_session_id, t.user_id]
);
if (sourceRows.length > 0) {
const sourceHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours);
const restoredHours = sourceHours + parseFloat(t.hours);
await conn.query(
'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND user_id = ?',
[restoredHours >= 8 ? null : restoredHours, t.source_session_id, t.user_id]
);
}
// 4. 이동 로그 삭제
await conn.query('DELETE FROM tbm_transfers WHERE transfer_id = ?', [transferId]);
await conn.commit();
return { success: true };
} catch (err) {
try { await conn.rollback(); } catch (e) {}
throw err;
} finally {
conn.release();
}
},
/**
* 당일 이동 내역 조회
*/
async getTransfersByDate(date) {
const db = await getDb();
const sql = `
SELECT
t.*,
w.worker_name,
w.job_type,
sl.worker_name as source_leader_name,
dl.worker_name as dest_leader_name,
u.name as initiated_by_name
FROM tbm_transfers t
INNER JOIN workers w ON t.user_id = w.user_id
LEFT JOIN tbm_sessions ss ON t.source_session_id = ss.session_id
LEFT JOIN workers sl ON ss.leader_id = sl.user_id
LEFT JOIN tbm_sessions ds ON t.dest_session_id = ds.session_id
LEFT JOIN workers dl ON ds.leader_id = dl.user_id
LEFT JOIN sso_users u ON t.initiated_by = u.user_id
WHERE t.transfer_date = ?
ORDER BY t.created_at DESC
`;
const [rows] = await db.query(sql, [date]);
return rows;
},
/**
* 당일 전 작업자 배정 현황 조회
*/
async getWorkerAssignmentsByDate(date) {
const db = await getDb();
// 1. 해당 날짜의 모든 배정 가져오기
const [assignments] = await db.query(`
SELECT
ta.user_id,
ta.session_id,
ta.work_hours,
w.worker_name,
w.job_type,
s.leader_id,
lw.worker_name as leader_name,
s.status as session_status
FROM tbm_team_assignments ta
INNER JOIN tbm_sessions s ON ta.session_id = s.session_id
INNER JOIN workers w ON ta.user_id = w.user_id
LEFT JOIN workers lw ON s.leader_id = lw.user_id
WHERE s.session_date = ?
ORDER BY w.worker_name
`, [date]);
// 2. 모든 작업자 가져오기 (배정 안 된 사람도 포함)
const [allWorkers] = await db.query(
"SELECT user_id, worker_name, job_type FROM workers WHERE status = 'active' AND department = '생산' ORDER BY worker_name"
);
// 3. 작업자별 배정 현황 구성
const workerMap = {};
allWorkers.forEach(w => {
workerMap[w.user_id] = {
user_id: w.user_id,
worker_name: w.worker_name,
job_type: w.job_type,
sessions: [],
total_hours: 0,
available: true
};
});
assignments.forEach(a => {
const hours = a.work_hours === null ? 8 : parseFloat(a.work_hours);
if (workerMap[a.user_id]) {
workerMap[a.user_id].sessions.push({
session_id: a.session_id,
leader_name: a.leader_name,
work_hours: hours,
session_status: a.session_status
});
workerMap[a.user_id].total_hours += hours;
}
});
Object.values(workerMap).forEach(w => {
w.available = w.total_hours < 8;
});
return Object.values(workerMap);
}
};
module.exports = TbmTransferModel;