v2.2.4: implement auto_overtime + overtime_unit + notif_before_minutes; remove 3 dead settings

Implemented (previously UI-only with no business effect):
- auto_overtime: when OFF, prompt user on clock-out before banking
- overtime_unit: 15/30/60-min truncation choice now actually applied
- notification_before_minutes: 30-min hardcode -> user-configurable 1~120

Removed dead keys (no readers in business logic):
- auto_detect_boot, notification_enabled, annual_leave_used

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
KINDNICK
2026-04-30 17:29:44 +09:00
co-authored by Claude Opus 4.7
parent b91b229731
commit 14d88656fe
10 changed files with 172 additions and 42 deletions
+50 -15
View File
@@ -1081,18 +1081,21 @@ class MainWindow(QMainWindow):
# 오늘의 외출 시간 가져오기
break_minutes = self.db.get_total_break_minutes_today()
# 적립 단위(분) — 사용자 설정. 기본 30, 옵션 15/60.
unit_minutes = self.db.get_setting_int('overtime_unit', 30)
if unit_minutes not in (15, 30, 60):
unit_minutes = 30
if is_non_working_day:
# 주말/공휴일: 모든 시간을 연장근무로 처리 (외출 시간 제외)
work_minutes = int(total_hours * 60)
if self.lunch_break_enabled:
work_minutes -= self.time_calc.lunch_duration_minutes # 점심시간 제외
work_minutes -= self.time_calc.lunch_duration_minutes
if self.dinner_break_enabled:
work_minutes -= self.time_calc.dinner_duration_minutes # 저녁시간 제외
work_minutes -= break_minutes # 외출시간 제외
# 음수 방지
work_minutes -= self.time_calc.dinner_duration_minutes
work_minutes -= break_minutes
work_minutes = max(0, work_minutes)
# 30분 단위로 절삭
overtime_earned = (work_minutes // 30) * 30
overtime_earned = (work_minutes // unit_minutes) * unit_minutes
overtime_actual = work_minutes
else:
# 평일: 정상 연장근무 계산 (외출 시간 포함)
@@ -1100,9 +1103,27 @@ class MainWindow(QMainWindow):
self.clock_in_time, now,
include_lunch=self.lunch_break_enabled,
include_dinner=self.dinner_break_enabled,
break_minutes=break_minutes
break_minutes=break_minutes,
unit_minutes=unit_minutes,
)
# AUTO_OVERTIME 가드: 자동 적립 OFF + 적립할 게 있으면 사용자에게 확인
auto_overtime = self.db.get_setting_bool('auto_overtime', True)
if not auto_overtime and overtime_earned > 0:
from utils.time_format import format_hours_minutes
time_str = format_hours_minutes(overtime_earned, omit_zero_minutes=True)
actual_str = format_hours_minutes(overtime_actual, omit_zero_minutes=True)
ask = QMessageBox.question(
self,
"연장근무 적립 확인",
f"연장근무 {actual_str} 발생, {time_str} 적립 대상입니다.\n\n"
f"적립하시겠습니까?\n"
f"(아니오 선택 시 이번 퇴근분은 적립되지 않습니다)",
QMessageBox.Yes | QMessageBox.No,
)
if ask != QMessageBox.Yes:
overtime_earned = 0 # 적립 스킵 (overtime_actual은 기록용으로 유지)
# DB 업데이트
today = datetime.now().date().isoformat()
clock_out_str = now.strftime("%H:%M:%S")
@@ -1601,16 +1622,30 @@ class MainWindow(QMainWindow):
def check_for_updates(self, silent: bool = False):
"""업데이트 확인. silent=True면 새 버전 있을 때만 알림 (시작 시 자동 체크용)."""
from core.version import __version__
from utils.updater_client import check_for_update, download_update, apply_update
from utils.updater_client import (
check_for_update, download_update, apply_update,
UP_TO_DATE, NETWORK_ERROR, NO_RELEASE, NO_ASSET,
)
info = check_for_update(__version__)
info, reason = check_for_update(__version__)
if info is None:
if not silent:
QMessageBox.information(
self,
"업데이트 확인",
f"현재 최신 버전입니다 (v{__version__})."
)
if silent:
return
# 사용자가 명시적으로 트리거한 경우만 메시지 표시
messages = {
UP_TO_DATE: ("업데이트 확인", f"현재 최신 버전입니다 (v{__version__})."),
NETWORK_ERROR: ("연결 실패",
"업데이트 서버에 연결할 수 없습니다.\n"
"네트워크 상태를 확인해 주세요."),
NO_RELEASE: ("릴리스 없음",
"업데이트 저장소에서 릴리스를 찾을 수 없습니다.\n"
"(저장소 비공개 또는 첫 릴리스 전)"),
NO_ASSET: ("자산 누락",
"새 버전은 있지만 다운로드 가능한 main.exe 자산이 없습니다.\n"
"관리자에게 문의하세요."),
}
title, body = messages.get(reason, ("업데이트 확인", "알 수 없는 응답입니다."))
QMessageBox.information(self, title, body)
return
# 빌드된 환경이 아니면 (개발 .py) 실제 적용 불가 — 알림만
+23
View File
@@ -18,6 +18,7 @@ from core.settings_keys import (
WORK_HOURS, WORK_MINUTES, LUNCH_DURATION_MINUTES, DINNER_DURATION_MINUTES,
AUTO_LUNCH, AUTO_BREAK_ON_LOCK, AUTO_OVERTIME,
NOTIF_CLOCK_OUT, NOTIF_LUNCH, NOTIF_OVERTIME, NOTIF_HEALTH,
NOTIFICATION_BEFORE_MINUTES,
THEME, TIME_FORMAT, LANGUAGE, OVERTIME_UNIT, ANNUAL_LEAVE_DAYS,
INITIAL_OVERTIME_MINUTES, INITIAL_LEAVE_USED_HOURS,
DB_PATH_OVERRIDE, CLOCK_IN_ON_UNLOCK,
@@ -275,6 +276,22 @@ class SettingsView(QDialog):
check_row2.addWidget(self.health_notification_check)
layout.addLayout(check_row2)
# 퇴근 N분 전 알림 시점 설정
before_row = QHBoxLayout()
before_label = QLabel("퇴근 알림 시점:")
before_label.setFixedWidth(110)
self.notif_before_spin = QSpinBox()
self.notif_before_spin.setRange(1, 120)
self.notif_before_spin.setSingleStep(5)
self.notif_before_spin.setValue(30)
self.notif_before_spin.setSuffix(" 분 전")
self.notif_before_spin.setFixedWidth(110)
self.notif_before_spin.setToolTip("퇴근 임박 알림이 표시될 시점 (분 단위)")
before_row.addWidget(before_label)
before_row.addWidget(self.notif_before_spin)
before_row.addStretch()
layout.addLayout(before_row)
# 시간 형식 + 테마 한 줄에
format_row = QHBoxLayout()
time_format_label = QLabel("시간 형식:")
@@ -796,6 +813,11 @@ class SettingsView(QDialog):
self.lunch_notification_check.setChecked(settings.get(NOTIF_LUNCH, True))
self.overtime_notification_check.setChecked(settings.get(NOTIF_OVERTIME, True))
self.health_notification_check.setChecked(settings.get(NOTIF_HEALTH, True))
if hasattr(self, 'notif_before_spin'):
try:
self.notif_before_spin.setValue(int(settings.get(NOTIFICATION_BEFORE_MINUTES, 30)))
except (ValueError, TypeError):
self.notif_before_spin.setValue(30)
# 시간 형식 (콤보박스는 문자열로 저장하므로 변환)
time_format = settings.get(TIME_FORMAT, '24')
@@ -879,6 +901,7 @@ class SettingsView(QDialog):
NOTIF_LUNCH: self.lunch_notification_check.isChecked(),
NOTIF_OVERTIME: self.overtime_notification_check.isChecked(),
NOTIF_HEALTH: self.health_notification_check.isChecked(),
NOTIFICATION_BEFORE_MINUTES: self.notif_before_spin.value(),
TIME_FORMAT: self.time_format_combo.currentData(),
OVERTIME_UNIT: self.overtime_unit_combo.currentData(),
AUTO_OVERTIME: self.auto_overtime_check.isChecked(),