- 월별 캘린더 UI로 작업 현황을 한눈에 확인 가능 - 미입력(빨강), 부분입력(주황), 확인필요(보라), 이상없음(초록) 상태 표시 - 범례 아이콘(●)을 사용한 직관적인 상태 표시 - 날짜 클릭 시 해당일 작업자별 상세 현황 모달 - 작업자 클릭 시 개별 작업 입력/수정 모달 - 휴가 처리 기능 (연차, 반차, 반반차, 조퇴) - 월별 집계 데이터 최적화로 API 호출 최소화 백엔드: - monthly_worker_status, monthly_summary 테이블 추가 - 자동 집계 stored procedure 및 trigger 구현 - 확인필요(12시간 초과) 상태 감지 로직 - 출석 관리 시스템 확장 프론트엔드: - 캘린더 그리드 UI 구현 - 상태별 색상 및 아이콘 표시 - 모달 기반 상세 정보 표시 - 반응형 디자인 적용
360 lines
14 KiB
SQL
360 lines
14 KiB
SQL
-- 근로 및 휴가 관리를 위한 테이블 확장
|
|
-- 작성일: 2025-11-03
|
|
|
|
-- 1. 근로 유형 테이블 생성
|
|
CREATE TABLE IF NOT EXISTS work_attendance_types (
|
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
type_code VARCHAR(20) NOT NULL UNIQUE COMMENT '근로 유형 코드',
|
|
type_name VARCHAR(50) NOT NULL COMMENT '근로 유형명',
|
|
description TEXT COMMENT '설명',
|
|
is_active BOOLEAN DEFAULT TRUE COMMENT '활성 상태',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) COMMENT='근로 유형 관리 테이블';
|
|
|
|
-- 2. 휴가 유형 테이블 생성
|
|
CREATE TABLE IF NOT EXISTS vacation_types (
|
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
type_code VARCHAR(20) NOT NULL UNIQUE COMMENT '휴가 유형 코드',
|
|
type_name VARCHAR(50) NOT NULL COMMENT '휴가 유형명',
|
|
hours_deduction DECIMAL(4,2) NOT NULL COMMENT '차감 시간',
|
|
description TEXT COMMENT '설명',
|
|
is_active BOOLEAN DEFAULT TRUE COMMENT '활성 상태',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) COMMENT='휴가 유형 관리 테이블';
|
|
|
|
-- 3. 일일 근태 기록 테이블 생성
|
|
CREATE TABLE IF NOT EXISTS daily_attendance_records (
|
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
record_date DATE NOT NULL COMMENT '기록 날짜',
|
|
worker_id INT NOT NULL COMMENT '작업자 ID',
|
|
total_work_hours DECIMAL(4,2) DEFAULT 0 COMMENT '총 작업 시간',
|
|
attendance_type_id INT COMMENT '근로 유형 ID',
|
|
vacation_type_id INT NULL COMMENT '휴가 유형 ID',
|
|
is_vacation_processed BOOLEAN DEFAULT FALSE COMMENT '휴가 처리 여부',
|
|
overtime_approved BOOLEAN DEFAULT FALSE COMMENT '초과근무 승인 여부',
|
|
overtime_approved_by INT NULL COMMENT '초과근무 승인자 ID',
|
|
overtime_approved_at TIMESTAMP NULL COMMENT '초과근무 승인 시간',
|
|
status ENUM('incomplete', 'partial', 'complete', 'overtime', 'vacation', 'error') DEFAULT 'incomplete' COMMENT '상태',
|
|
notes TEXT COMMENT '비고',
|
|
created_by INT NOT NULL COMMENT '생성자 ID',
|
|
updated_by INT NULL COMMENT '수정자 ID',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
-- 외래키 제약조건
|
|
FOREIGN KEY (worker_id) REFERENCES workers(worker_id) ON DELETE CASCADE,
|
|
FOREIGN KEY (attendance_type_id) REFERENCES work_attendance_types(id),
|
|
FOREIGN KEY (vacation_type_id) REFERENCES vacation_types(id),
|
|
FOREIGN KEY (overtime_approved_by) REFERENCES users(user_id),
|
|
FOREIGN KEY (created_by) REFERENCES users(user_id),
|
|
FOREIGN KEY (updated_by) REFERENCES users(user_id),
|
|
|
|
-- 유니크 제약조건 (작업자별 날짜별 하나의 기록)
|
|
UNIQUE KEY unique_worker_date (worker_id, record_date),
|
|
|
|
-- 인덱스
|
|
INDEX idx_record_date (record_date),
|
|
INDEX idx_worker_date (worker_id, record_date),
|
|
INDEX idx_status (status)
|
|
) COMMENT='일일 근태 기록 테이블';
|
|
|
|
-- 4. 휴가 잔여 관리 테이블 생성
|
|
CREATE TABLE IF NOT EXISTS worker_vacation_balance (
|
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
|
worker_id INT NOT NULL COMMENT '작업자 ID',
|
|
year YEAR NOT NULL COMMENT '연도',
|
|
total_annual_leave DECIMAL(4,2) DEFAULT 15.0 COMMENT '연간 총 연차 (일)',
|
|
used_annual_leave DECIMAL(4,2) DEFAULT 0 COMMENT '사용한 연차 (일)',
|
|
remaining_annual_leave DECIMAL(4,2) GENERATED ALWAYS AS (total_annual_leave - used_annual_leave) STORED COMMENT '잔여 연차 (일)',
|
|
notes TEXT COMMENT '비고',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
-- 외래키 제약조건
|
|
FOREIGN KEY (worker_id) REFERENCES workers(worker_id) ON DELETE CASCADE,
|
|
|
|
-- 유니크 제약조건 (작업자별 연도별 하나의 기록)
|
|
UNIQUE KEY unique_worker_year (worker_id, year),
|
|
|
|
-- 인덱스
|
|
INDEX idx_worker_year (worker_id, year)
|
|
) COMMENT='작업자별 휴가 잔여 관리 테이블';
|
|
|
|
-- 5. 기본 데이터 삽입
|
|
|
|
-- 근로 유형 기본 데이터
|
|
INSERT INTO work_attendance_types (type_code, type_name, description) VALUES
|
|
('REGULAR', '정시근로', '8시간 정규 근무'),
|
|
('OVERTIME', '연장근로', '8시간 초과 근무'),
|
|
('PARTIAL', '부분근로', '8시간 미만 근무'),
|
|
('VACATION', '휴가근로', '휴가와 함께하는 부분 근무')
|
|
ON DUPLICATE KEY UPDATE
|
|
type_name = VALUES(type_name),
|
|
description = VALUES(description);
|
|
|
|
-- 휴가 유형 기본 데이터
|
|
INSERT INTO vacation_types (type_code, type_name, hours_deduction, description) VALUES
|
|
('ANNUAL_FULL', '연차', 8.0, '하루 전체 연차'),
|
|
('ANNUAL_HALF', '반차', 4.0, '반일 연차'),
|
|
('ANNUAL_QUARTER', '반반차', 2.0, '1/4일 연차'),
|
|
('SICK_FULL', '병가', 8.0, '하루 전체 병가'),
|
|
('SICK_HALF', '반일병가', 4.0, '반일 병가'),
|
|
('PERSONAL_FULL', '개인사유', 8.0, '개인사유로 인한 휴가'),
|
|
('PERSONAL_HALF', '반일개인사유', 4.0, '반일 개인사유 휴가')
|
|
ON DUPLICATE KEY UPDATE
|
|
type_name = VALUES(type_name),
|
|
hours_deduction = VALUES(hours_deduction),
|
|
description = VALUES(description);
|
|
|
|
-- 6. daily_work_reports 테이블에 근태 기록 연결 컬럼 추가
|
|
ALTER TABLE daily_work_reports
|
|
ADD COLUMN attendance_record_id INT NULL COMMENT '근태 기록 ID' AFTER updated_by,
|
|
ADD INDEX idx_attendance_record (attendance_record_id);
|
|
|
|
-- 외래키 제약조건 추가 (나중에 데이터 정리 후)
|
|
-- ALTER TABLE daily_work_reports
|
|
-- ADD FOREIGN KEY (attendance_record_id) REFERENCES daily_attendance_records(id);
|
|
|
|
-- 7. 휴가 전용 작업 유형 추가 (work_types 테이블에)
|
|
INSERT INTO work_types (name, description, is_active) VALUES
|
|
('휴가', '연차, 반차, 병가 등 휴가 처리용', TRUE)
|
|
ON DUPLICATE KEY UPDATE
|
|
description = VALUES(description),
|
|
is_active = VALUES(is_active);
|
|
|
|
-- 8. 뷰 생성 - 일일 근태 현황 조회용
|
|
CREATE OR REPLACE VIEW v_daily_attendance_summary AS
|
|
SELECT
|
|
dar.id,
|
|
dar.record_date,
|
|
dar.worker_id,
|
|
w.worker_name,
|
|
w.job_type,
|
|
dar.total_work_hours,
|
|
wat.type_name as attendance_type,
|
|
vt.type_name as vacation_type,
|
|
dar.is_vacation_processed,
|
|
dar.overtime_approved,
|
|
dar.status,
|
|
CASE
|
|
WHEN dar.status = 'incomplete' THEN '미입력'
|
|
WHEN dar.status = 'partial' THEN '부분입력'
|
|
WHEN dar.status = 'complete' THEN '정시근로'
|
|
WHEN dar.status = 'overtime' THEN '연장근로'
|
|
WHEN dar.status = 'vacation' THEN '휴가'
|
|
WHEN dar.status = 'error' THEN '오류'
|
|
ELSE '알수없음'
|
|
END as status_text,
|
|
dar.notes,
|
|
dar.created_at,
|
|
dar.updated_at
|
|
FROM daily_attendance_records dar
|
|
LEFT JOIN workers w ON dar.worker_id = w.worker_id
|
|
LEFT JOIN work_attendance_types wat ON dar.attendance_type_id = wat.id
|
|
LEFT JOIN vacation_types vt ON dar.vacation_type_id = vt.id
|
|
ORDER BY dar.record_date DESC, w.worker_name;
|
|
|
|
-- 9. 트리거 생성 - daily_work_reports 변경 시 근태 기록 자동 업데이트
|
|
DELIMITER //
|
|
|
|
CREATE OR REPLACE TRIGGER tr_update_attendance_on_work_report
|
|
AFTER INSERT ON daily_work_reports
|
|
FOR EACH ROW
|
|
BEGIN
|
|
DECLARE total_hours DECIMAL(4,2);
|
|
DECLARE attendance_type INT;
|
|
DECLARE vacation_type INT;
|
|
DECLARE record_status VARCHAR(20);
|
|
DECLARE existing_record_id INT;
|
|
|
|
-- 해당 작업자의 해당 날짜 총 작업시간 계산
|
|
SELECT COALESCE(SUM(work_hours), 0) INTO total_hours
|
|
FROM daily_work_reports
|
|
WHERE worker_id = NEW.worker_id
|
|
AND report_date = NEW.report_date;
|
|
|
|
-- 휴가 처리 여부 확인 (work_type_id가 휴가용인지)
|
|
SELECT id INTO vacation_type
|
|
FROM vacation_types
|
|
WHERE (total_hours = 0 AND type_code = 'ANNUAL_FULL')
|
|
OR (total_hours = 4 AND type_code = 'ANNUAL_HALF')
|
|
OR (total_hours = 6 AND type_code = 'ANNUAL_QUARTER')
|
|
LIMIT 1;
|
|
|
|
-- 근로 유형 결정
|
|
IF total_hours = 0 THEN
|
|
SELECT id INTO attendance_type FROM work_attendance_types WHERE type_code = 'PARTIAL';
|
|
SET record_status = 'incomplete';
|
|
ELSEIF total_hours < 8 THEN
|
|
SELECT id INTO attendance_type FROM work_attendance_types WHERE type_code = 'PARTIAL';
|
|
SET record_status = 'partial';
|
|
ELSEIF total_hours = 8 THEN
|
|
SELECT id INTO attendance_type FROM work_attendance_types WHERE type_code = 'REGULAR';
|
|
SET record_status = 'complete';
|
|
ELSE
|
|
SELECT id INTO attendance_type FROM work_attendance_types WHERE type_code = 'OVERTIME';
|
|
SET record_status = 'overtime';
|
|
END IF;
|
|
|
|
-- 휴가 처리된 경우 상태 조정
|
|
IF vacation_type IS NOT NULL THEN
|
|
SET record_status = 'vacation';
|
|
END IF;
|
|
|
|
-- 기존 근태 기록 확인
|
|
SELECT id INTO existing_record_id
|
|
FROM daily_attendance_records
|
|
WHERE worker_id = NEW.worker_id AND record_date = NEW.report_date;
|
|
|
|
-- 근태 기록 업데이트 또는 생성
|
|
IF existing_record_id IS NOT NULL THEN
|
|
UPDATE daily_attendance_records
|
|
SET
|
|
total_work_hours = total_hours,
|
|
attendance_type_id = attendance_type,
|
|
vacation_type_id = vacation_type,
|
|
is_vacation_processed = (vacation_type IS NOT NULL),
|
|
status = record_status,
|
|
updated_by = NEW.created_by,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = existing_record_id;
|
|
ELSE
|
|
INSERT INTO daily_attendance_records (
|
|
record_date, worker_id, total_work_hours, attendance_type_id,
|
|
vacation_type_id, is_vacation_processed, status, created_by
|
|
) VALUES (
|
|
NEW.report_date, NEW.worker_id, total_hours, attendance_type,
|
|
vacation_type, (vacation_type IS NOT NULL), record_status, NEW.created_by
|
|
);
|
|
END IF;
|
|
END//
|
|
|
|
DELIMITER ;
|
|
|
|
-- 10. 기존 데이터 마이그레이션을 위한 프로시저
|
|
DELIMITER //
|
|
|
|
CREATE OR REPLACE PROCEDURE sp_migrate_existing_work_reports()
|
|
BEGIN
|
|
DECLARE done INT DEFAULT FALSE;
|
|
DECLARE v_worker_id INT;
|
|
DECLARE v_report_date DATE;
|
|
DECLARE cur CURSOR FOR
|
|
SELECT DISTINCT worker_id, report_date
|
|
FROM daily_work_reports
|
|
ORDER BY report_date DESC, worker_id;
|
|
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
|
|
|
|
OPEN cur;
|
|
|
|
read_loop: LOOP
|
|
FETCH cur INTO v_worker_id, v_report_date;
|
|
IF done THEN
|
|
LEAVE read_loop;
|
|
END IF;
|
|
|
|
-- 각 작업자별 날짜별로 근태 기록 생성/업데이트
|
|
CALL sp_update_attendance_record(v_worker_id, v_report_date);
|
|
END LOOP;
|
|
|
|
CLOSE cur;
|
|
END//
|
|
|
|
CREATE OR REPLACE PROCEDURE sp_update_attendance_record(
|
|
IN p_worker_id INT,
|
|
IN p_report_date DATE
|
|
)
|
|
BEGIN
|
|
DECLARE total_hours DECIMAL(4,2);
|
|
DECLARE attendance_type INT;
|
|
DECLARE vacation_type INT;
|
|
DECLARE record_status VARCHAR(20);
|
|
DECLARE existing_record_id INT;
|
|
DECLARE has_vacation_work INT DEFAULT 0;
|
|
|
|
-- 해당 작업자의 해당 날짜 총 작업시간 계산
|
|
SELECT COALESCE(SUM(work_hours), 0) INTO total_hours
|
|
FROM daily_work_reports
|
|
WHERE worker_id = p_worker_id
|
|
AND report_date = p_report_date;
|
|
|
|
-- 휴가 관련 작업이 있는지 확인 (work_type_id = 999 또는 휴가 관련)
|
|
SELECT COUNT(*) INTO has_vacation_work
|
|
FROM daily_work_reports dwr
|
|
JOIN work_types wt ON dwr.work_type_id = wt.id
|
|
WHERE dwr.worker_id = p_worker_id
|
|
AND dwr.report_date = p_report_date
|
|
AND (wt.name LIKE '%휴가%' OR wt.name LIKE '%연차%' OR wt.name LIKE '%반차%');
|
|
|
|
-- 휴가 유형 결정
|
|
IF has_vacation_work > 0 THEN
|
|
IF total_hours = 0 THEN
|
|
SELECT id INTO vacation_type FROM vacation_types WHERE type_code = 'ANNUAL_FULL' LIMIT 1;
|
|
ELSEIF total_hours = 4 THEN
|
|
SELECT id INTO vacation_type FROM vacation_types WHERE type_code = 'ANNUAL_HALF' LIMIT 1;
|
|
ELSEIF total_hours = 6 THEN
|
|
SELECT id INTO vacation_type FROM vacation_types WHERE type_code = 'ANNUAL_QUARTER' LIMIT 1;
|
|
END IF;
|
|
END IF;
|
|
|
|
-- 근로 유형 및 상태 결정
|
|
IF vacation_type IS NOT NULL THEN
|
|
SELECT id INTO attendance_type FROM work_attendance_types WHERE type_code = 'VACATION';
|
|
SET record_status = 'vacation';
|
|
ELSEIF total_hours = 0 THEN
|
|
SELECT id INTO attendance_type FROM work_attendance_types WHERE type_code = 'PARTIAL';
|
|
SET record_status = 'incomplete';
|
|
ELSEIF total_hours < 8 THEN
|
|
SELECT id INTO attendance_type FROM work_attendance_types WHERE type_code = 'PARTIAL';
|
|
SET record_status = 'partial';
|
|
ELSEIF total_hours = 8 THEN
|
|
SELECT id INTO attendance_type FROM work_attendance_types WHERE type_code = 'REGULAR';
|
|
SET record_status = 'complete';
|
|
ELSE
|
|
SELECT id INTO attendance_type FROM work_attendance_types WHERE type_code = 'OVERTIME';
|
|
SET record_status = 'overtime';
|
|
END IF;
|
|
|
|
-- 기존 근태 기록 확인
|
|
SELECT id INTO existing_record_id
|
|
FROM daily_attendance_records
|
|
WHERE worker_id = p_worker_id AND record_date = p_report_date;
|
|
|
|
-- 근태 기록 업데이트 또는 생성
|
|
IF existing_record_id IS NOT NULL THEN
|
|
UPDATE daily_attendance_records
|
|
SET
|
|
total_work_hours = total_hours,
|
|
attendance_type_id = attendance_type,
|
|
vacation_type_id = vacation_type,
|
|
is_vacation_processed = (vacation_type IS NOT NULL),
|
|
status = record_status,
|
|
updated_by = 1,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = existing_record_id;
|
|
ELSE
|
|
INSERT INTO daily_attendance_records (
|
|
record_date, worker_id, total_work_hours, attendance_type_id,
|
|
vacation_type_id, is_vacation_processed, status, created_by
|
|
) VALUES (
|
|
p_report_date, p_worker_id, total_hours, attendance_type,
|
|
vacation_type, (vacation_type IS NOT NULL), record_status, 1
|
|
);
|
|
END IF;
|
|
END//
|
|
|
|
DELIMITER ;
|
|
|
|
-- 11. 권한 및 인덱스 최적화
|
|
-- 추가 인덱스 생성
|
|
CREATE INDEX idx_daily_work_reports_worker_date ON daily_work_reports(worker_id, report_date);
|
|
CREATE INDEX idx_daily_work_reports_work_type ON daily_work_reports(work_type_id);
|
|
|
|
-- 마이그레이션 실행 (주석 해제하여 실행)
|
|
-- CALL sp_migrate_existing_work_reports();
|
|
|
|
-- 완료 메시지
|
|
SELECT 'DB 확장 완료: 근로 및 휴가 관리 시스템이 성공적으로 구축되었습니다.' as message;
|