feat: 대시보드 작업장 현황 지도 구현
- 실시간 작업장 현황을 지도로 시각화 - 작업장 관리 페이지에서 정의한 구역 정보 활용 - TBM 작업자 및 방문자 현황 표시 주요 변경사항: - dashboard.html: 작업장 현황 섹션 추가 (기존 작업 현황 테이블 제거) - workplace-status.js: 지도 렌더링 및 데이터 통합 로직 구현 - modern-dashboard.js: 삭제된 DOM 요소 조건부 체크 추가 시각화 방식: - 인원 없음: 회색 테두리 + 작업장 이름 - 내부 작업자: 파란색 영역 + 인원 수 - 외부 방문자: 보라색 영역 + 인원 수 - 둘 다: 초록색 영역 + 총 인원 수 기술 구현: - Canvas API 기반 사각형 영역 렌더링 - map-regions API를 통한 데이터 일관성 보장 - 클릭 이벤트로 상세 정보 모달 표시 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
288
api.hyungi.net/models/vacationBalanceModel.js
Normal file
288
api.hyungi.net/models/vacationBalanceModel.js
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* vacationBalanceModel.js
|
||||
* 휴가 잔액 관련 데이터베이스 쿼리 모델
|
||||
*/
|
||||
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
const vacationBalanceModel = {
|
||||
/**
|
||||
* 특정 작업자의 모든 휴가 잔액 조회 (특정 연도)
|
||||
*/
|
||||
async getByWorkerAndYear(workerId, year, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
vbd.*,
|
||||
vt.type_name,
|
||||
vt.type_code,
|
||||
vt.priority,
|
||||
vt.is_special
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.worker_id = ? AND vbd.year = ?
|
||||
ORDER BY vt.priority ASC, vt.type_name ASC
|
||||
`;
|
||||
const [rows] = await db.query(query, [workerId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 특정 작업자의 특정 휴가 유형 잔액 조회
|
||||
*/
|
||||
async getByWorkerTypeYear(workerId, vacationTypeId, year, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
vbd.*,
|
||||
vt.type_name,
|
||||
vt.type_code
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.worker_id = ?
|
||||
AND vbd.vacation_type_id = ?
|
||||
AND vbd.year = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [workerId, vacationTypeId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 모든 작업자의 휴가 잔액 조회 (특정 연도)
|
||||
* - 연간 연차 현황 차트용
|
||||
*/
|
||||
async getAllByYear(year, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
vbd.*,
|
||||
w.worker_name,
|
||||
w.employment_status,
|
||||
vt.type_name,
|
||||
vt.type_code,
|
||||
vt.priority
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN workers w ON vbd.worker_id = w.worker_id
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.year = ?
|
||||
AND w.employment_status = 'employed'
|
||||
ORDER BY w.worker_name ASC, vt.priority ASC
|
||||
`;
|
||||
const [rows] = await db.query(query, [year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 잔액 생성
|
||||
*/
|
||||
async create(balanceData, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `INSERT INTO vacation_balance_details SET ?`;
|
||||
const [rows] = await db.query(query, balanceData);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 잔액 수정
|
||||
*/
|
||||
async update(id, updateData, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `UPDATE vacation_balance_details SET ? WHERE id = ?`;
|
||||
const [rows] = await db.query(query, [updateData, id]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 휴가 잔액 삭제
|
||||
*/
|
||||
async delete(id, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `DELETE FROM vacation_balance_details WHERE id = ?`;
|
||||
const [rows] = await db.query(query, [id]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업자의 휴가 사용 일수 업데이트 (차감)
|
||||
* - 휴가 신청 승인 시 호출
|
||||
*/
|
||||
async deductDays(workerId, vacationTypeId, year, daysToDeduct, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
UPDATE vacation_balance_details
|
||||
SET used_days = used_days + ?,
|
||||
updated_at = NOW()
|
||||
WHERE worker_id = ?
|
||||
AND vacation_type_id = ?
|
||||
AND year = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [daysToDeduct, workerId, vacationTypeId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업자의 휴가 사용 일수 복구 (취소)
|
||||
* - 휴가 신청 취소/거부 시 호출
|
||||
*/
|
||||
async restoreDays(workerId, vacationTypeId, year, daysToRestore, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
UPDATE vacation_balance_details
|
||||
SET used_days = GREATEST(0, used_days - ?),
|
||||
updated_at = NOW()
|
||||
WHERE worker_id = ?
|
||||
AND vacation_type_id = ?
|
||||
AND year = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [daysToRestore, workerId, vacationTypeId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 특정 작업자의 사용 가능한 휴가 일수 확인
|
||||
* - 우선순위가 높은 순서대로 차감 가능 여부 확인
|
||||
*/
|
||||
async getAvailableVacationDays(workerId, year, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
vbd.id,
|
||||
vbd.vacation_type_id,
|
||||
vt.type_name,
|
||||
vt.type_code,
|
||||
vt.priority,
|
||||
vbd.total_days,
|
||||
vbd.used_days,
|
||||
vbd.remaining_days
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.worker_id = ?
|
||||
AND vbd.year = ?
|
||||
AND vbd.remaining_days > 0
|
||||
ORDER BY vt.priority ASC
|
||||
`;
|
||||
const [rows] = await db.query(query, [workerId, year]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업자별 휴가 잔액 일괄 생성 (연도별)
|
||||
* - 매년 초 또는 입사 시 사용
|
||||
*/
|
||||
async bulkCreate(balances, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
|
||||
if (!balances || balances.length === 0) {
|
||||
return callback(new Error('생성할 휴가 잔액 데이터가 없습니다'));
|
||||
}
|
||||
|
||||
const query = `INSERT INTO vacation_balance_details
|
||||
(worker_id, vacation_type_id, year, total_days, used_days, notes, created_by)
|
||||
VALUES ?`;
|
||||
|
||||
const values = balances.map(b => [
|
||||
b.worker_id,
|
||||
b.vacation_type_id,
|
||||
b.year,
|
||||
b.total_days || 0,
|
||||
b.used_days || 0,
|
||||
b.notes || null,
|
||||
b.created_by
|
||||
]);
|
||||
|
||||
const [rows] = await db.query(query, [values]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 근속년수 기반 연차 일수 계산 (한국 근로기준법)
|
||||
* @param {Date} hireDate - 입사일
|
||||
* @param {number} targetYear - 대상 연도
|
||||
* @returns {number} - 부여받을 연차 일수
|
||||
*/
|
||||
calculateAnnualLeaveDays(hireDate, targetYear) {
|
||||
const hire = new Date(hireDate);
|
||||
const targetDate = new Date(targetYear, 0, 1);
|
||||
|
||||
// 근속 월수 계산
|
||||
const monthsDiff = (targetDate.getFullYear() - hire.getFullYear()) * 12
|
||||
+ (targetDate.getMonth() - hire.getMonth());
|
||||
|
||||
// 1년 미만: 월 1일
|
||||
if (monthsDiff < 12) {
|
||||
return Math.floor(monthsDiff);
|
||||
}
|
||||
|
||||
// 1년 이상: 15일 기본 + 2년마다 1일 추가 (최대 25일)
|
||||
const yearsWorked = Math.floor(monthsDiff / 12);
|
||||
const additionalDays = Math.floor((yearsWorked - 1) / 2);
|
||||
|
||||
return Math.min(15 + additionalDays, 25);
|
||||
},
|
||||
|
||||
/**
|
||||
* 특정 ID로 휴가 잔액 조회
|
||||
*/
|
||||
async getById(id, callback) {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const query = `
|
||||
SELECT
|
||||
vbd.*,
|
||||
w.worker_name,
|
||||
vt.type_name,
|
||||
vt.type_code
|
||||
FROM vacation_balance_details vbd
|
||||
INNER JOIN workers w ON vbd.worker_id = w.worker_id
|
||||
INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id
|
||||
WHERE vbd.id = ?
|
||||
`;
|
||||
const [rows] = await db.query(query, [id]);
|
||||
callback(null, rows);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = vacationBalanceModel;
|
||||
Reference in New Issue
Block a user