NOTES | 技術筆記
用 LINE+Google 試算表做多店排班與 GPS 打卡:定位驗證要注意什麼
員工在 LINE 裡打卡、店長在 Google 試算表排班,不用買打卡機也不用租主機。這篇整理架構怎麼拆、手機定位的誤差怎麼處理、怎麼確認打卡的人是本人,以及 GPS 打卡擋得住什麼、擋不住什麼。
重點摘要
- 架構用三樣現成服務:LINE LIFF(員工在 LINE 裡打開的打卡網頁)、Google Apps Script(判斷能不能打卡、寫紀錄)、Google 試算表(店家、員工、班表、打卡紀錄、月工時)。
- 定位要看兩個數字:離店多遠,以及手機自己回報的誤差。誤差太大就不收,不然「誤差 500 公尺、中心點剛好在店裡」也會過。
- 打卡半徑每間店自己設:百貨美食街、地下街的定位偏差大,騎樓店面可以收緊。
- 不要相信前端傳來的身分:用 LIFF 拿 ID token,後端呼叫 LINE 的驗證 API 換出 user ID。
- GPS 打卡能大幅減少「請同事代打」,但擋不住刻意偽造定位。重點是留下失敗紀錄、搭配班表比對,讓店長看得到異常。
可以直接試用的 Demo:多店排班+GPS 打卡(禾日食堂)。
架構怎麼拆
| 角色 | 用什麼 | 做什麼 |
|---|---|---|
| 員工 | LINE 裡的 LIFF 網頁 | 看今天的班、按上班/下班,送出一次定位 |
| 後端 | Google Apps Script 網頁應用程式 | 驗證身分、比對距離與班表、寫入打卡紀錄 |
| 店長 | Google 試算表 | 排班、看打卡紀錄、月底看工時彙總 |
試算表分成五個分頁:
- 店家:店家 ID、店名、緯度、經度、打卡半徑、誤差上限
- 員工:員工 ID、姓名、員工代碼、LINE user ID(第一次打卡時綁定)
- 班表:班別 ID、日期、店家 ID、員工 ID、開始、結束
- 打卡紀錄:時間、員工、店家、班別、上班/下班、經緯度、誤差、距離、成功/失敗、原因
- 月工時:每月 1 日由時間驅動觸發條件自動產生
多店的好處在這裡最明顯:班表一張表就能看到所有分店,員工被調去支援別間店,排在那間店就能在那間店打卡,不用另外設定。
定位驗證:距離之外,還要看誤差
瀏覽器的定位 API(navigator.geolocation)回傳的不只是經緯度,還有一個 accuracy,代表手機估計的誤差半徑(公尺)。[1] 室外開 GPS 通常是十幾公尺;在室內、地下室、只靠 Wi-Fi 或基地台定位時,可能是幾百公尺甚至更大。
所以判斷要分兩步:
- 誤差超過上限就不收,請員工開 GPS、走到門口或窗邊再試。
- 誤差可以接受,再算距離。距離用 haversine 公式算兩點之間的大圓距離,跟店家設定的半徑比。
function checkLocation(pos, store) {
const d = haversineMeters(pos.lat, pos.lng, store.lat, store.lng);
if (pos.accuracy > store.maxAccuracyM) return { ok: false, reason: '定位誤差太大' };
if (d > store.radiusM) return { ok: false, reason: `距離店家約 ${Math.round(d)} 公尺` };
return { ok: true };
}
幾個實作細節:
- 呼叫定位時設
maximumAge: 0,不要用手機快取的舊位置;enableHighAccuracy: true要求盡量用 GPS。[2] - 定位只能在 HTTPS 頁面使用,LIFF 頁面本來就要 HTTPS,放 GitHub Pages 或 Cloudflare Pages 都可以。[2]
- 員工第一次會被問要不要允許定位。被拒絕時要給清楚的提示(到手機設定允許 LINE 使用位置),不要只顯示「失敗」。
- 半徑不要設太小。店家座標通常取自地圖上的一個點,店面本身就有寬度;先設 80~120 公尺,看一週的失敗紀錄再調。
確認打卡的人是本人
LIFF 頁面可以拿到使用者的 LINE 資料,但從前端送到後端的 user ID 是可以改的。比較穩的做法是:
- 前端用
liff.getIDToken()拿到 ID token。[3] - 後端(Apps Script)把 ID token 和 Channel ID 送到 LINE 的驗證 API,由 LINE 回傳這個 token 屬於哪個 user ID。[4]
- 用這個 user ID 去「員工」分頁找人。
第一次使用的員工還沒綁定,請他輸入店長給的員工代碼,把 LINE user ID 寫進那一列。一個代碼只能綁一次,換手機或離職由店長清掉重綁。
班表比對:在範圍內也不一定能打卡
定位過了,還要確認「現在是不是你在這間店的班」:
- 上班卡:開班前 30 分鐘到班結束之間可以打;超過寬限時間(例如 5 分鐘)記為遲到。
- 下班卡:開班之後到下班後 3 小時之間可以打,忘了打的隔天由店長補登。
- 跨午夜的班(例如 18:00–02:00):結束時間比開始早就加一天;凌晨打下班卡要能找回前一天的班。
- 同一個班不能重複打;沒有上班卡就不能打下班卡。
- 兩個人同時按:Apps Script 用
LockService鎖住讀寫,避免兩筆紀錄互相看不到。
班表時間在試算表裡如果是「時間」格式,Apps Script 讀到的是 1899 年的日期物件,換時區時可能差幾分鐘。讀班表時用 getDisplayValues() 讀畫面上的文字(例如 09:30),最不容易出錯。
月底工時怎麼算
每個班有兩種時間:
- 實際在店:上班卡到下班卡之間的時間。
- 計薪工時:實際在店時間和班表時間重疊的部分。早到、晚退不自動算進去,加班另外申請;遲到照實扣。
缺下班卡、缺勤、遲到次數一起列出來,店長月底只要看這張表,有疑問再回頭查打卡紀錄。這只是常見的一種算法,實際要依各店的工作規則調整。
依《勞動基準法》第 30 條,雇主要置備勞工出勤紀錄並保存五年,[5] 打卡紀錄分頁不要定期清空,可以每年另存一份。
GPS 打卡擋得住什麼、擋不住什麼
擋得住的:
- 請同事幫忙打卡(每個人只能用自己的 LINE 打)
- 人還在路上就先打卡
- 打錯分店、打到不是自己的班
擋不住的:
- 刻意用模擬定位工具偽造位置。手機端的資料終究由手機提供,網頁沒辦法完全確認。
所以這套系統的定位是「降低代打卡、讓異常看得見」,不是防弊的鐵證。實務上的補強:
- 失敗也要留紀錄:太遠、定位不準、不在班表時段,店長都看得到。某個人常常在邊界被擋,就該問一下。
- 搭配班表與現場:店長本來就知道誰有沒有來,系統是幫忙記帳,不是取代管理。
- 要更嚴可以加第二道:店內貼每天更換的 QR code,或限定連上店內 Wi-Fi 才能打卡。
另外,定位屬於個人資料。只在按下打卡的那一刻讀一次、不做背景追蹤,並事先跟員工說明用途。