From 6f2df815f5ec6199d55e591f55811656746234e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IT=E7=8B=97?= Date: Tue, 21 Jul 2026 13:38:58 +0800 Subject: [PATCH] =?UTF-8?q?Simplify=20Attendance=20export=20status=20group?= =?UTF-8?q?s=20to=206=20categories:=20=E6=AD=A3=E5=B8=B8|=E9=81=B2?= =?UTF-8?q?=E5=88=B0|=E6=97=A9=E9=80=80|=E5=8A=A0=E7=8F=AD|=E7=95=B0?= =?UTF-8?q?=E5=B8=B8/=E7=BC=BA=E5=8B=A4|=E5=81=87=E6=9C=9F/=E8=AB=8B?= =?UTF-8?q?=E5=81=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/main.py | 58 ++++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/backend/main.py b/backend/main.py index eaa6e0a..6b5ad32 100644 --- a/backend/main.py +++ b/backend/main.py @@ -3407,31 +3407,45 @@ async def export_excel( earliest = min((r.date for r in rows if r.date), default=None) overview.merge_range("F7:G7", earliest.isoformat() if earliest else "-", kpi_value_fmt) - # Status distribution table (B10:D) + # Status distribution table — simplified 6-group (B10:D) overview.write("B10", "出勤狀態分佈", section_fmt) overview.write("B11", "Status", header_fmt_ov) overview.write("C11", "數量", header_fmt_ov) overview.write("D11", "百分比", header_fmt_ov) - status_codes_order = ["normal", "late", "late_early", "late_ot", "early", "early_ot", "ot", "abnormal", "missing", - "holiday", "al", "sl", "cl", "mixed_leave"] - status_labels = { - "normal": "正常", "late": "遲到", "late_early": "遲到+早走", - "late_ot": "遲到+加班", "early": "早走", "early_ot": "早走+加班", - "ot": "加班", "abnormal": "異常", "missing": "缺勤", - "holiday": "🏖️公假", "al": "年假 AL", "sl": "病假 SL", "cl": "補鐘 CL", - "mixed_leave": "混合假", + # Simplified 6-group status + _grp_status = { + "正常": 0, "遲到": 0, "早退": 0, "加班": 0, "異常/缺勤": 0, "假期/請假": 0, "(空)": 0, } - for i, code in enumerate(status_codes_order): - count = sum(1 for r in rows if (r.status_code or "").lower() == code) - overview.write(11 + i, 1, status_labels.get(code, code), cell_fmt_ov) + for r in rows: + c = (r.status_code or "").lower() + if not c.strip(): + _grp_status["(空)"] += 1 + elif c == "normal": + _grp_status["正常"] += 1 + elif "late" in c and "early" in c: + _grp_status["遲到"] += 1 + elif "late" in c: + _grp_status["遲到"] += 1 + elif "early" in c: + _grp_status["早退"] += 1 + elif "ot" in c: + _grp_status["加班"] += 1 + elif c in ("abnormal", "missing", "", " "): + _grp_status["異常/缺勤"] += 1 + else: + _grp_status["假期/請假"] += 1 + _total = len(rows) + for i, (label, count) in enumerate(_grp_status.items()): + overview.write(11 + i, 1, label, cell_fmt_ov) overview.write(11 + i, 2, count, cell_fmt_ov) - pct = (count / len(rows) * 100) if rows else 0 + pct = (count / _total * 100) if _total else 0 overview.write(11 + i, 3, f"{pct:.1f}%", cell_fmt_ov) - unk = sum(1 for r in rows if not (r.status_code or "").strip()) - overview.write(11 + len(status_codes_order), 1, "(空)", cell_fmt_ov) - overview.write(11 + len(status_codes_order), 2, unk, cell_fmt_ov) - pct_unk = (unk / len(rows) * 100) if rows else 0 - overview.write(11 + len(status_codes_order), 3, f"{pct_unk:.1f}%", cell_fmt_ov) + _n_status_groups = len(_grp_status) + # Also update KPI counters to match simplified grouping + n_late_grp = _grp_status["遲到"] + n_early_grp = _grp_status["早退"] + n_ot_grp = _grp_status["加班"] + n_abn_grp = _grp_status["異常/缺勤"] # Department TOP 10 table (F10:G) overview.write("F10", "部門記錄數 (TOP 10)", section_fmt) @@ -3449,8 +3463,8 @@ async def export_excel( status_pie = wb.add_chart({"type": "pie"}) status_pie.add_series({ "name": "出勤狀態分佈", - "categories": ["總覽", 11, 1, 11 + len(status_codes_order), 1], - "values": ["總覽", 11, 2, 11 + len(status_codes_order), 2], + "categories": ["總覽", 11, 1, 11 + _n_status_groups, 1], + "values": ["總覽", 11, 2, 11 + _n_status_groups, 2], "data_labels": {"percentage": True, "category": False, "position": "outside_end"}, }) status_pie.set_title({"name": "出勤狀態分佈 (Pie)"}) @@ -3480,8 +3494,8 @@ async def export_excel( status_col_chart = wb.add_chart({"type": "column"}) status_col_chart.add_series({ "name": "出勤狀態 數量", - "categories": ["總覽", 11, 1, 11 + len(status_codes_order) - 1, 1], - "values": ["總覽", 11, 2, 11 + len(status_codes_order) - 1, 2], + "categories": ["總覽", 11, 1, 11 + _n_status_groups - 1, 1], + "values": ["總覽", 11, 2, 11 + _n_status_groups - 1, 2], "fill": {"color": "#10B981"}, "border": {"color": "#065F46"}, "data_labels": {"value": True},