If late/early/OT > 30min → mark as abnormal status (override status_code+text, zero out minutes)

This commit is contained in:
IT狗
2026-07-21 17:52:36 +08:00
parent f41dad6127
commit ef57d7cd59
+27 -13
View File
@@ -299,7 +299,7 @@ async def upload_excel(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""Upload Excel file saves file AND imports rows into SQL table."""
"""Upload Excel file - saves file AND imports rows into SQL table."""
if section not in ["attendance", "accident"]:
raise HTTPException(status_code=400, detail="Invalid section")
@@ -742,6 +742,20 @@ def calculate_attendance_status(check_in, check_out, shift_start, shift_end):
result["status_code"] = "normal"
result["status_text"] = " / ".join(status_parts) if status_parts else "正常"
# If late / early / OT > 30 min → mark as abnormal (override after status_text is set)
if result["late_minutes"] > 30 or result["early_minutes"] > 30 or result["ot_minutes"] > 30:
result["status_code"] = "abnormal"
if result["late_minutes"] > 30:
result["status_text"] = f"異常-遲到{result['late_minutes']}"
elif result["early_minutes"] > 30:
result["status_text"] = f"異常-早退{result['early_minutes']}"
else:
result["status_text"] = f"異常-OT{result['ot_minutes']}"
result["late_minutes"] = 0
result["early_minutes"] = 0
result["ot_minutes"] = 0
return result
@@ -2121,7 +2135,7 @@ async def _import_accident_to_db(db: Session, headers, rows, user_id: int) -> di
Col 8 涉及的患者或相關人員 -> patient_or_staff
Col 9 事件描述 -> long_description
Col 10 如有事件相關照片可提供-> incident_photos
Col 11 處理情況已採取的行動-> action_taken
Col 11 處理情況/已採取的行動-> action_taken
Col 12 需要管理層介入的事項 -> needs_escalation
Col 13 第 12 欄 -> (skipped)
Col 14 分數 -> incident_score
@@ -2839,7 +2853,7 @@ async def accident_export_excel(
# User types case_no in C5 of 個案查詢 sheet.
# xlsxwriter requires single quotes around sheet names containing non-ASCII.
# Use proper xlsxwriter hyperlink element (write_url with internal:Sheet!Cell).
# HYPERLINK formula would cache value 0 write_url sets display text directly.
# HYPERLINK formula would cache value 0 - write_url sets display text directly.
overview.write_url(row_n, 6,
f"internal:個案查詢!A1",
link_fmt,
@@ -2847,7 +2861,7 @@ async def accident_export_excel(
tip=f"跳到「個案查詢」輸入個案 ID {r.id}")
overview.set_row(row_n, 18)
# ============ Charts block 統計圖表 (Sheet 1 總覽 右側 columns I-N) ============
# ============ Charts block - 統計圖表 (Sheet 1 總覽 右側 columns I-N) ============
# 4 charts: Severity Pie, Severity Column, Department Bar TOP 10, Monthly Trend Line
# Position: I column (right of KPI + 個案索引 blocks), so no overlap with case index.
@@ -2957,7 +2971,7 @@ async def accident_export_excel(
# ============ Sheet 3 (built FIRST so detail-VLOOKUP can reference it): 明細 ============
# Build it now so that 個案查詢 formulas can resolve its range properly.
# However, xlsxwriter's defined_name allows forward reference; formula text is
# what matters at runtime even if the sheet is added later, the formula string
# what matters at runtime - even if the sheet is added later, the formula string
# '明細!A:O' will be valid when Excel opens the file.
# We define 明細 here so that the formula range 明細!A2:O{max_row} resolves.
# But xlsxwriter requires sheets to be added in the order they're defined.
@@ -3017,12 +3031,12 @@ async def accident_export_excel(
"align": "center", "num_format": "@"})
# B5 = label only (NOT merged with C5). Otherwise user-typed text goes to
# B5 (top-left of merge) instead of C5 where the formula references.
detail.write("B5", "輸入個案 ID", input_label_fmt)
detail.write("B5", "輸入個案 ID:", input_label_fmt)
# C5 = case_no input cell (NOT merged)
detail.write_string("C5", "", text_input_fmt)
# Hint
detail.merge_range("B6:C6",
"提示個案 ID 喺「總覽」個案索引或「明細」表嘅 id 欄。輸入後下面自動顯示。",
"提示:個案 ID 喺「總覽」個案索引或「明細」表嘅 id 欄。輸入後下面自動顯示。",
subtitle_fmt)
# Detail rows: each pulls from 明細!<col><row> via VLOOKUP keyed on id (col A)
@@ -3061,7 +3075,7 @@ async def accident_export_excel(
col_sev = col_index_lookup.get("severity", 6)
col_emp = col_index_lookup.get("employee_name", 3)
col_desc = col_index_lookup.get("description", 5)
# Use INDIRECT or simply use cell reference for ID but ID varies per row.
# Use INDIRECT or simply use cell reference for ID - but ID varies per row.
# Easier: each row's ID is in B<row_n>; build formula with that.
# VLOOKUP needs lookup_value, table, col_index, FALSE.
# table 明細!$A:$O.
@@ -3108,13 +3122,13 @@ async def accident_export_excel(
compare_start_row = list_header_row + n_list_rows + 3
detail.write(f"B{compare_start_row}", "兩個個案 並列比較 Side-by-Side", section_fmt)
detail.write(f"B{compare_start_row + 1}",
"輸入兩個個案 ID (左個案 A, 右個案 B)對比每個欄位。空白表示無資料。",
"輸入兩個個案 ID (左個案 A, 右個案 B),對比每個欄位。空白表示無資料。",
subtitle_fmt)
cmp_label_row = compare_start_row + 2
detail.write(cmp_label_row, 1, "個案 A ID", input_label_fmt)
detail.write(cmp_label_row, 1, "個案 A ID:", input_label_fmt)
detail.write_string(cmp_label_row, 2, "", text_input_fmt)
detail.write(cmp_label_row, 4, "個案 B ID", input_label_fmt)
detail.write(cmp_label_row, 4, "個案 B ID:", input_label_fmt)
detail.write_string(cmp_label_row, 5, "", text_input_fmt)
detail.set_column("F:F", 50)
@@ -3146,7 +3160,7 @@ async def accident_export_excel(
# Hint at top
detail.merge_range(f"B{cmp_data_start + len(detail_fields) + 2}:C{cmp_data_start + len(detail_fields) + 2}",
"💡 全部公式 (VLOOKUP)無需啟用巨集。Excel/Numbers 都正常運作。",
"💡 全部公式 (VLOOKUP),無需啟用巨集。Excel/Numbers 都正常運作。",
subtitle_fmt)
# ============ Sheet 3: 明細 ============
@@ -3411,7 +3425,7 @@ 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 simplified 6-group (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)