From 21a01df97afd15fb5a2c3950bf47a22cbbb2508f Mon Sep 17 00:00:00 2001 From: KINDNICK Date: Tue, 1 Sep 2026 12:41:00 +0900 Subject: [PATCH] =?UTF-8?q?v2.13.1:=20=EC=9D=BC=EC=9D=BC=EB=B3=B4=EA=B3=A0?= =?UTF-8?q?=20=EC=8B=9C=EA=B0=84=EC=97=B0=EC=B0=A8=20=EB=B6=84=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed: - 30분 연차(0.0625일)가 int(days * 8) 절삭으로 "0시간"으로 표시되어 보고서 수신자가 사용 내역을 알 수 없던 문제. 분 단위까지 표시. - 보고서만 1일=8시간으로 하드코딩되어 단축근무(7h30m) 사용자의 반차가 4시간으로 표시되던 오류. DB 정본(days * work_minutes) 기준으로 통일. Added: - utils.time_format.format_leave_amount(days, work_minutes) 공용 헬퍼 + 자체 검사 9건 (python utils/time_format.py) - i18n 키 report.leave_used_days_time (ko/en) 테스트: 유닛 200 / 통합 53 / GUI 8 / i18n 5 통과. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 15 +++++++++++++++ core/i18n.py | 2 ++ core/version.py | 2 +- ui/main_window.py | 42 ++++++++++++++++++------------------------ utils/time_format.py | 37 +++++++++++++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4e3f7b..667edd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [2.13.1] — 2026-09-01 + +### Fixed +- **일일보고에서 시간 단위 연차가 "0시간"으로 표시되던 문제** — 30분 연차(0.0625일)가 + `int(days * 8)` 절삭으로 0시간이 되어 보고서를 받는 담당자가 사용 내역을 알 수 없었다. + 분 단위까지 표시하도록 수정 (`30분`, `1시간 30분`, `1일 4시간`). +- **단축근무 사용자의 연차 시간 환산 오류** — 보고서만 1일=8시간으로 하드코딩되어 있어 + 7h30m 근무자의 반차가 4시간으로 표시됐다. DB 정본(`days * work_minutes`)과 동일한 + 기준으로 통일 — 이제 3시간 45분. + +### Added +- `utils.time_format.format_leave_amount(days, work_minutes)` — 연차 일수를 + (일수, 나머지 분)으로 분해하는 공용 헬퍼 + 자체 검사(`python utils/time_format.py`). +- i18n 키 `report.leave_used_days_time` (`{days}일 {time}` / `{days}d {time}`). + ## [2.13.0] — 2026-08-12 ### Fixed — 익일(자정 넘김) 퇴근 diff --git a/core/i18n.py b/core/i18n.py index cb39318..ec25ec3 100644 --- a/core/i18n.py +++ b/core/i18n.py @@ -450,6 +450,7 @@ _DICT = { 'report.leave_used': '🌴 연차 사용: {value}', 'report.leave_used_days_hours': '{days}일 {hours}시간', 'report.leave_used_days': '{days}일', + 'report.leave_used_days_time': '{days}일 {time}', 'report.leave_used_hours': '{hours}시간', 'report.leave_detail': ' - {type}: {time}{memo}', 'report.title': '📋 일일 근무 보고서 - {date}', @@ -1613,6 +1614,7 @@ _DICT = { 'report.leave_used': '🌴 Leave used: {value}', 'report.leave_used_days_hours': '{days}d {hours}h', 'report.leave_used_days': '{days}d', + 'report.leave_used_days_time': '{days}d {time}', 'report.leave_used_hours': '{hours}h', 'report.leave_detail': ' - {type}: {time}{memo}', 'report.title': '📋 Daily Work Report - {date}', diff --git a/core/version.py b/core/version.py index 8e6ec31..e6a5c50 100644 --- a/core/version.py +++ b/core/version.py @@ -4,4 +4,4 @@ 릴리스 시 이 값을 올린 후 git tag → push. CHANGELOG.md의 최상단 항목과 일치시킬 것. """ -__version__ = '2.13.0' +__version__ = '2.13.1' diff --git a/ui/main_window.py b/ui/main_window.py index abead33..811f5be 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -34,7 +34,7 @@ from ui.settings_view import SettingsView from ui.break_view import BreakView from core.notifier import Notifier from utils.system_tray import SystemTrayIcon -from utils.time_format import format_hours_minutes +from utils.time_format import format_hours_minutes, format_leave_amount from ui.styles import get_theme, ThemeColors, apply_dark_titlebar from utils.debug_log import dlog @@ -2265,6 +2265,21 @@ class MainWindow(QMainWindow): if self.is_clocked_in and self.clock_in_time: self.update_display() + def _format_leave_used(self, days: float) -> str: + """연차 사용량을 '30분' / '3시간 45분' / '1일 4시간' 형태로. + + 기존에는 `int(days * 8)`로 시간만 뽑아 30분 연차(0.0625일)가 "0시간"으로 + 표시됐다. 환산 기준도 DB 정본(`days * work_minutes`)에 맞춰 단축근무 + 사용자의 반차가 4시간으로 뻥튀기되지 않게 한다. + """ + d, rem = format_leave_amount(days, self.db.get_work_minutes()) + if d and rem: + return tr('report.leave_used_days_time', days=d, + time=format_hours_minutes(rem, omit_zero_minutes=True)) + if d: + return tr('report.leave_used_days', days=d) + return format_hours_minutes(rem, omit_zero_minutes=True) + def _append_meal_section(self, report_lines, today: str, label: str, flag: bool, records: list, default_min: int) -> None: """일일 보고서의 점심/저녁 섹션 출력. @@ -2458,16 +2473,7 @@ class MainWindow(QMainWindow): if filtered_leave_records: total_leave_days = sum(r['days'] for r in filtered_leave_records) - if total_leave_days >= 1: - days = int(total_leave_days) - hours = int((total_leave_days - days) * 8) - if hours > 0: - value = tr('report.leave_used_days_hours', days=days, hours=hours) - else: - value = tr('report.leave_used_days', days=days) - else: - hours = int(total_leave_days * 8) - value = tr('report.leave_used_hours', hours=hours) + value = self._format_leave_used(total_leave_days) report_lines.append(tr('report.leave_used', value=value)) for lr in filtered_leave_records: @@ -2483,19 +2489,7 @@ class MainWindow(QMainWindow): '반반차': tr('leave.type.quarter') }.get(lr.get('leave_type', ''), lr.get('leave_type', tr('leave.type.annual'))) - days_used = lr['days'] - - # 일수를 시간으로 표시 - if days_used >= 1: - d = int(days_used) - h = int((days_used - d) * 8) - if h > 0: - time_str = tr('report.leave_used_days_hours', days=d, hours=h) - else: - time_str = tr('report.leave_used_days', days=d) - else: - h = int(days_used * 8) - time_str = tr('report.leave_used_hours', hours=h) + time_str = self._format_leave_used(lr['days']) memo = f" - {lr['memo']}" if lr.get('memo') else "" report_lines.append(f" - {leave_type_name}: {time_str}{memo}") diff --git a/utils/time_format.py b/utils/time_format.py index 5748ab1..8107233 100644 --- a/utils/time_format.py +++ b/utils/time_format.py @@ -31,3 +31,40 @@ def format_hours_minutes(total_minutes: int, *, omit_zero_minutes: bool = False) if m == 0: return f"{h}시간" return f"{h}시간 {m}분" + + +def format_leave_amount(days: float, work_minutes: int) -> tuple: + """연차 일수를 (완전한 일수, 나머지 분)으로 분해. + + `int(days * 8)`로 시간만 뽑던 표시가 30분 연차(0.0625일)를 "0시간"으로 + 삼켜버리던 문제를 막는다. 환산 기준은 DB 정본과 동일한 work_minutes + (`Database.get_leave_minutes_for`가 `days * get_work_minutes()`를 쓴다) — + 단축근무 7h30m 사용자의 반차는 4시간이 아니라 3시간 45분이다. + + Args: + days: 연차 일수 (1.0 / 0.5 / 0.25 / 0.0625 …) + work_minutes: 1일 근무 분 (기본 480) + + Returns: + (일수, 나머지 분) — 예: 480분 기준 1.5일 → (1, 240), 0.0625일 → (0, 30) + """ + unit = int(work_minutes) if work_minutes and work_minutes > 0 else 480 + total_minutes = int(round(float(days) * unit)) + return divmod(total_minutes, unit) + + +if __name__ == '__main__': + # 표준 8시간(480분) 기준 + assert format_leave_amount(0.0625, 480) == (0, 30), '30분 연차' + assert format_leave_amount(0.125, 480) == (0, 60), '1시간 연차' + assert format_leave_amount(0.25, 480) == (0, 120), '반반차' + assert format_leave_amount(0.5, 480) == (0, 240), '반차' + assert format_leave_amount(1.0, 480) == (1, 0), '종일' + assert format_leave_amount(1.5, 480) == (1, 240), '1.5일' + # 단축근무 7h30m(450분) — 반차는 3시간 45분 + assert format_leave_amount(0.5, 450) == (0, 225), '단축근무 반차' + assert format_leave_amount(1.0, 450) == (1, 0), '단축근무 종일' + # work_minutes 이상값은 480으로 폴백 + assert format_leave_amount(0.5, 0) == (0, 240), '0 폴백' + assert format_hours_minutes(30, omit_zero_minutes=True) == '30분' + print('format_leave_amount 자체 검사 통과')