/** * CompBase attendance: scan missing clock-out (應刷未刷) and auto-fill via RID page. */ function setActionStatus(text, ok) { const el = document.getElementById("actionStatus"); if (!el) return; el.textContent = text || ""; el.className = "settings-status" + (ok === true ? " ok" : ok === false ? " err" : ""); } function escapeHtml(text) { return String(text) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function getDays() { const n = Number(document.getElementById("days")?.value || 14); if (!Number.isFinite(n) || n < 1) return 14; return Math.min(62, Math.floor(n)); } function getOutTimeMode() { return document.getElementById("outTimeMode")?.value || "expected"; } function isDryRun() { return Boolean(document.getElementById("dryRun")?.checked); } async function refreshConn() { const line = document.getElementById("connLine"); try { const data = await api("/api/compbase/status"); if (data.default_days && document.getElementById("days")) { document.getElementById("days").value = data.default_days; } if (data.out_time_mode && document.getElementById("outTimeMode")) { document.getElementById("outTimeMode").value = data.out_time_mode; } if (!line) return; if (!data.has_credentials) { line.textContent = "未設定帳密(請到設定登入 PTS,或填 CompBase 帳密)"; return; } const name = data.display_name || data.username || "—"; const emp = data.emp_id ? ` · ${data.emp_id}` : ""; const src = data.credential_source === "compbase" ? "CompBase 帳密" : "PTS 帳密"; line.textContent = data.connected ? `CompBase ✓ · ${name}${emp} · ${src}` : `CompBase ✗ · ${data.error || "連線失敗"} · ${src}`; } catch (err) { if (line) line.textContent = err.message; } } function rowStatusLabel(row, kind) { if (kind === "filled") { if (row.dry_run) return `試跑 · 將送 ${escapeHtml(row.chosen_out_time || "—")}`; return `已送出 · ${escapeHtml(row.chosen_out_time || "—")}${row.message ? " · " + escapeHtml(row.message) : ""}`; } if (kind === "error") { return `失敗 · ${escapeHtml(row.error || row.message || "unknown")}`; } const reason = row.reason || row.status || "skipped"; const map = { no_rid: "無 RID(可能缺刷進)", missing_in: "缺刷進", missing_out: "缺刷退但無連結", other: "其他異常", }; return map[reason] || escapeHtml(String(reason)); } function renderResults(data, mode) { const root = document.getElementById("resultView"); if (!root) return; const filled = data.filled || data.fillable || []; const skipped = data.skipped || []; const errors = data.errors || []; const isScan = mode === "scan"; const summaryParts = [ `區間 ${escapeHtml(data.start || "—")} → ${escapeHtml(data.end || "—")}`, isScan ? `可補 ${data.fillable_count ?? filled.length}` : `完成 ${data.filled_count ?? filled.length}`, `略過 ${data.skipped_count ?? skipped.length}`, ]; if (!isScan) { summaryParts.push(`失敗 ${data.error_count ?? errors.length}`); if (data.dry_run) summaryParts.push("試跑"); } const blocks = []; blocks.push(`
${summaryParts.join(" · ")}
`); function table(title, rows, kind) { if (!rows.length) return ""; const body = rows .map((r) => { return `${escapeHtml(r.rid || "—")}| 日期 | RID | 刷進 | 應下班 | 狀態 |
|---|
近 ${escapeHtml(String(data.days || ""))} 天沒有可處理的漏刷退。
`); } root.innerHTML = blocks.join(""); } async function runScan() { const days = getDays(); setActionStatus(`掃描近 ${days} 天…`); try { const data = await api("/api/compbase/scan", { method: "POST", body: JSON.stringify({ days }), }); renderResults(data, "scan"); setActionStatus( `掃描完成:可補 ${data.fillable_count ?? 0}、略過 ${data.skipped_count ?? 0}`, true ); } catch (err) { setActionStatus(err.message, false); } } async function runFill() { const days = getDays(); const dryRun = isDryRun(); const outTimeMode = getOutTimeMode(); if (!dryRun) { const ok = window.confirm( `將對近 ${days} 天的「應刷未刷」送出刷退補填到 CompBase。\n確定繼續?` ); if (!ok) return; } setActionStatus(dryRun ? `試跑近 ${days} 天…` : `補填近 ${days} 天…`); try { const data = await api("/api/compbase/fill", { method: "POST", body: JSON.stringify({ days, dry_run: dryRun, out_time_mode: outTimeMode, }), }); renderResults(data, "fill"); const ok = (data.error_count ?? 0) === 0; setActionStatus( `${dryRun ? "試跑" : "補填"}完成:成功 ${data.filled_count ?? 0}、略過 ${data.skipped_count ?? 0}、失敗 ${data.error_count ?? 0}`, ok ); } catch (err) { setActionStatus(err.message, false); } } function init() { document.getElementById("scanBtn")?.addEventListener("click", runScan); document.getElementById("fillBtn")?.addEventListener("click", runFill); refreshConn(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); }