老闆 dogfood StaffsMate 後抓出 4 個方向:(1) 排班 / 薪資 / 制度 / 打卡 4 個 tab 都「很簡陋」、
(2) 多個欄位「沒有對齊 POS App」(例如營業時間應該用 reservation_slots 不是 business_hours)、
(3) 薪資 tab 紅屏 crash String.characters getter not found(並行修中)、
(4) 4 層架構(Gate / Primitive / Repository / Orchestrator)幾乎完全缺失。
本份 Audit 把整個 staffsmate_flutter/(共 8,656 行 Dart,22 個 .dart 檔)逐一盤點,
列出 mismatch、missing feature、refactor priority。
boss_schedule_page.dart 一個檔 2,171 行,包含 10 個並行 Supabase query + 排班 / 換桌 / 違規檢查
/ 拖拉 / 班次模板 / 月檢視全部塞在一起。boss_payroll_page.dart 只算工時,完全沒有薪資計算邏輯。
boss_payroll_page.dart 180 行只做「員工數 / 打卡次數 / 總工時」。
沒有 base_salary、沒有 pay_grade、沒有勞健保、沒有加班費、沒有 payslip 產生、沒有匯款檔。
DB schema 早就有 staff.base_salary / payroll_type / has_labor_insurance / health_insurance_level 等欄位卻沒讀。boss_schedule_page.dart:120 仍在讀 business_hours.is_open
判斷公休(per memory feedback_no_business_hours.md 已 deprecate)。POS 全系統已改用 reservation_slots +
special_dates,StaffsMate 用錯來源 → 公休判斷可能跟 POS 不一致。AuthService + LaborLawService 兩個 service,
零個 Gate、零個 Orchestrator、零個 Repository、零個 Primitive。
每加一個新功能就再多一個「View 直接打 Supabase」的胖檔。String.charactersNoSuchMethodError: Class 'String' has no instance getter 'characters'。
根因:Dart 預設 String 沒有 .characters getter,要 import 'package:characters/characters.dart';。
boss_payroll_page.dart:5 已加 import 但用到 .characters.first 的其他 5 個地方還沒檢查。
| 檔案 | 用法 | 狀態 |
|---|---|---|
boss_payroll_page.dart:146 | name.characters.first 取首字頭像 | 已 import |
boss_schedule_page.dart:827 / 967 | empName[empName.length - 1](subscript,不是 .characters) | ⚠️ 不安全 中文字符 utf16 surrogate 可能炸 |
boss_staff_page.dart | 多處取 display_name[0] | ⚠️ 同上 |
boss_schedule_page.dart:1165 | display_name.substring(0, 1) | ⚠️ 同上 |
profile_page.dart | 頭像首字 | ⚠️ 同上 |
建議: 抽一個 StringExt 或 NameInitial.from(String) helper,
內部統一 import 'package:characters/characters.dart'; 再 name.characters.firstOrNull ?? '?'。
這樣未來 emoji 表情符 / 中日韓 surrogate pair 都不會炸。
StaffsMate 共使用 20 個 Supabase 表:
avatars、business_hours、clock_records、company_policies、
employee_station_skills、floor_zones、leave_policies、leave_requests、
reservation_slots、restaurant_members、restaurant_settings、restaurants、
schedules、shift_templates、special_dates、staff、
staff_invitations、staff_tip_settings、tip_events、user_profiles。
| 📍 位置 | StaffsMate 讀的 | POS 實際用的 | 影響 | 🔧 建議 |
|---|---|---|---|---|
boss_schedule_page.dart:120 |
business_hours.is_open 判定公休 |
POS 用 reservation_slots.is_enabled + special_dates.date_type |
⚠️ 嚴重 POS 若週一不開店是改 reservation_slots 而不是 business_hours,StaffsMate 公休標記會錯 |
刪除 business_hours query;用 reservation_slots(已存在於 query 8)+ special_dates 推導 _isClosedDay() |
boss_schedule_page.dart:727 |
station['target_hours'] — fallback 40 |
floor_zones 表沒有 target_hours 欄位,POS 用 min_regular_staff + min_trainee_staff |
⚠️ 死碼 永遠 fallback = 40,缺人警示永遠不準 | 改用 min_regular_staff * shift_hours 計算或加 migration 新增 target_hours_per_day |
boss_payroll_page.dart |
只讀 restaurant_members + clock_records |
POS 有 staff.base_salary / payroll_type / hire_date / pay_grade_id / has_labor_insurance / labor_insurance_level / health_insurance_level / health_insurance_dependents |
⚠️ 重大遺漏 完全沒讀薪資設定 → 「薪資」tab 沒有薪資 | 新增 staff 表 join,按 user_profile_id → staff row 取 base_salary / payroll_type 計算月薪 |
boss_payroll_page.dart:40 |
clock_records 用 employee_id = restaurant_members.user_id (= auth.user_id) |
POS 真正打卡 record 也是用 user_id(auth user),但 staff table 主鍵是 staff.id,不是 user_id |
⚠️ 注意 薪資要 link staff 必須走 user_profile_id 再 join |
明確記錄 employee_id 到底是 auth_user_id 還是 user_profile_id(看 clock_records.employee_id 實際型別) |
boss_schedule_page.dart:108 |
schedules.employee_id join user_profiles |
但 staff 表 的 user_id 跟 user_profile_id 是不同 column。POS 員工管理是看 staff.user_profile_id | ⚠️ 混亂 排班用 user_id, tip_settings 用 user_profile_id,員工同步可能漏 | 統一以 user_profile_id 為員工識別 key。schedules.employee_id 改 reference user_profile_id |
clock_page.dart:38 |
restaurant_settings.latitude / longitude + 100m 範圍 |
POS 實際存於同表 ✅ 對齊。但「100m」是 hardcode | ⚠️ 死寫 都市區 vs 郊區誤差不同,不能調 | 新增 restaurant_settings.clock_in_radius_meters 欄位,預設 100 |
boss_payroll_page.dart:170 |
_calculateHours 用 sorted in/out pair |
POS 沒有等價邏輯(POS 不算薪),但邏輯有 bug:if record 順序是 in/in/out 會算錯,pair 跨日也沒處理 | ⚠️ 計算錯誤 | 改為 group by date + per-day pair;考慮 forgotten clock-out 場景;24h+ 班直接 skip 並標 anomaly |
tip_settings_page.dart |
對齊 staff.customer_intro / customer_photo_url / job_position / job_title / show_to_customer + staff_tip_settings |
POS 同欄位 ✅ 已對齊(#W12 同批新增) | ✅ 對齊 | 但沒讀 restaurant_settings.tip_system_enabled — 老闆關了打賞系統,員工還能設定 → UI 應該 disabled + 顯示「老闆尚未啟用打賞系統」 |
boss_policy_page.dart:24 |
hardcode policy_type list(rank/promotion/salary/leave/overtime/benefit) | POS 沒有對應頁,但 company_policies.policy_type 沒有 enum constraint —— 寫什麼都可以 |
⚠️ 缺乏 validation | 後端加 CHECK constraint 或改用 enum 強制;前端清單同步 spec |
boss_schedule_page.dart:131 |
讀整月排班(monthRaw) |
POS 沒 query 排班,但每次切日期都 reload 整月 schedule + 5 個其他表,網路成本高 | ⚠️ 效能 | 切日只 reload _schedules(當日);月份切才 reload 月度資料 |
incoming_invitation.dart:73 |
staff_invitations |
POS 也用同表 ✅ 對齊 | ✅ 對齊 | — |
shift_swap_requests 表 |
StaffsMate 完全沒用到 | DB 有此表(暗示 POS 端 / 未來功能會用) | 缺漏 | 實作 shift swap 功能(員工互換班、老闆審) |
staff.preferred_shifts(ARRAY) |
StaffsMate 沒讀 | POS 也沒用,但排班自動化建議應該用到 | 未利用 | 排班 v2 加「按員工偏好班次自動指派」用此欄位 |
employment_type / employment_status / hire_date |
StaffsMate 沒讀 | POS 有,影響「試用期」「離職員工濾掉」 | ⚠️ 已離職員工會出現在排班 | 所有列員工 query 加 .eq('employment_status', 'active') 或排除 terminated |
business_hoursfeedback_no_business_hours.md:「不要加『營業時間』設定,
唯一營業判斷來源是『供餐時段』(reservation_slots)」
| 欄位 | business_hours | reservation_slots |
|---|---|---|
| 用途 | (deprecated) 一天只能設一個 is_open | 一天可多時段(午餐 / 晚餐 / brunch) |
| 欄位 | day_of_week, is_open | day_of_week, start_time, end_time, closing_time, name, is_enabled, interval_minutes, max_reservations |
| POS 行為 | 幾乎沒在用 | POS 訂位日曆 / 結帳閘 / 關帳閘 都靠這個 |
| special_dates 套用 | 無 | 有 closing_time_override + periods |
_businessHours field + index 4 query_isClosedDay(d) 改成:「該 weekday 在 reservation_slots 沒有任何 is_enabled=true 列」+「special_dates 該日 date_type='closed'」special_dates.closing_time_override 套用邏輯(POS 預約頁有,這邊也該尊重)按 CLAUDE.md 第一原則:所有商業邏輯 / 副作用應該走 4 層架構。
StaffsMate 目前架構覆蓋率:
| Feature | 檔案 | LOC | Gate | Primitive | Repository | Orchestrator | Refactor 優先級 |
|---|---|---|---|---|---|---|---|
| 排班 (boss) | boss_schedule_page.dart | 2,171 | ❌ | ❌ | ❌ | ❌ | P0 最複雜、最多分支 |
| 薪資 (boss) | boss_payroll_page.dart | 180 | ❌ | ❌ | ❌ | ❌ | P0 重寫時直接上 4 層 |
| 制度 (boss) | boss_policy_page.dart | 142 | ❌ | ❌ | ❌ | ❌ | P2 邏輯簡單先 defer |
| 打卡裝置 (boss) | boss_clock_device_page.dart | 100 | ❌ | ❌ | ❌ | ❌ | P2 |
| 站別 (boss) | station_config_page.dart | 318 | ❌ | ❌ | ❌ | ❌ | P1 技能升級規則應該抽 Gate |
| 員工管理 (boss) | boss_staff_page.dart | 472 | ❌ | ❌ | ❌ | ⚠️ | P1 auth_service 部分覆蓋 |
| 打卡 (employee) | clock_page.dart | 375 | ❌ | ❌ | ❌ | ❌ | P0 規則複雜 + 違規最常見 |
| 請假 (employee) | leave_page.dart | 101 | ❌ | ❌ | ❌ | ❌ | P1 假別餘額 / 截止日 規則應該 Gate |
| 排班檢視 (employee) | schedule_page.dart | 69 | ❌ | ❌ | ❌ | ❌ | P3 純 read 不急 |
| 制度查詢 (employee) | company_policy_page.dart | 90 | ❌ | ❌ | ❌ | ❌ | P3 |
| 個人 Profile | profile_page.dart | 341 | ❌ | ❌ | ❌ | ❌ | P2 |
| 打賞設定 | tip_settings_page.dart | 645 | ❌ | ❌ | ❌ | ❌ | P2 但已 ship,可慢慢拆 |
| 邀請流程 | auth_service.dart + incoming_invitation.dart | 913 | ❌ | ⚠️ | ⚠️ | ⚠️ | P1 已部分 Service 化 |
結論: 13 個 feature,0 個有 Gate,0 個有 Orchestrator, 0 個有 Repository。整個 staffsmate_flutter 4 層架構覆蓋率 = 0%。
打卡是最常觸發的功能,規則最多(geofence / verified / 在範圍內 / QR scan / 重複打卡 detect), 最適合作為「示範」refactor。建議拆分:
canClockIn(state) → Result — 是否允許上班打卡(在範圍 OR 掃 QR、無未配對的 clock_in、不在請假中)canClockOut(state) → Result — 是否允許下班打卡(必須有未配對的 clock_in)isInGeofence(pos, restaurant, radius) → boolparseClockQR(rawCode) → ClockQRPayload? — 解 staffsmate:// chefsmate:// JSON UUID 4 種格式insertClockRecord(payload) → Future<ClockRecord>fetchLastClockRecord(employeeId, restaurantId)fetchRestaurantLocation(restaurantId)ClockRepository — 包 insertClockRecord / fetchLast / fetchTodayRestaurantSettingsRepository — 包 location / radius / business_schedule_modeclockInOrchestrator(employeeId, pos, qrCode?) → Result<ClockRecord, ClockError> — 跑 Gate → 通過則 Primitive → 回 ResultclockOutOrchestrator(...) 同上
然後 clock_page.dart 變成純 UI:只 call orchestrator + 把 Result 翻成 SnackBar。
LaborLawService)— 連續工時、雙週工時、休息日staff.preferred_shifts 已存但沒用employee_station_skills 但只在 station_config 編輯,排班不檢查)staff.base_salary 算實際$shift_swap_requests 但 UI 沒實作整個 page 只做 3 件事:
沒有薪資。連時薪都沒讀。
staff.base_salary、staff.payroll_type(月薪/時薪/日薪/論件)LaborLawService 已有違規偵測但沒費率)has_labor_insurance / labor_insurance_level / custom_labor_insurance_amount / has_health_insurance / health_insurance_level / health_insurance_dependents 但沒讀leave_requestsLaborLawService 偵測平日超時,乘費率labor_insurance_level 計算雇主端負擔 + 員工扣繳Phase 2 進階: payslip PDF / 銀行匯款檔。Phase 3: 扣繳憑單。
company_policies.content_jsoncompany_policy_page.dart 唯讀檢視leave_policies 已存在但跟 company_policies('leave') 是兩套資料policy_acknowledgementsrestaurant_settings.clock_in_radius_metersstaff.customer_intro / customer_photo_url / job_position / job_title / show_to_customerstaff_tip_settings upsert 包含 7 個欄位avatars bucket 的 tips/qr/ 路徑tips/photo/restaurant_settings.tip_system_enabled — 老闆關了打賞,員工進這頁仍可設定,但客人端不會顯示。員工會困惑「我設定了為什麼客人看不到」。restaurant_settings.tip_system_enabled,false 時整頁顯示 banner「老闆尚未啟用打賞系統,您的設定會儲存但暫不會顯示給客人」+ disable enabled toggle。restaurant_settings.tip_distribution_mode — 老闆設定「平均分配 / 個人指定」會影響員工該不該設定(如果是平均分,個人 QR 沒意義)staff.customer_photo_url,需要驗證 RLS 是否允許未登入 anonymous client 讀(dogfood 必驗)restaurant_settings.tip_amount_options JSONB,員工頁可以預覽「客人會看到 50 / 100 / 200」schedule_page.dart:23 / leave_page.dart:79 / company_policy_page.dart:27。
每次 setState 或 ref 變化都重打 supabase。改用 FutureProvider 或 state-managed cache。clock_page.dart:50
onError catch { setState(() => _noCoordinates = true); } 在 catch block 沒 check mounted。boss_policy_page.dart:84(delete policy)/ boss_staff_page.dart:313/322(update member)/leave_page.dart:36(insert leave_requests)— 失敗 silentclock_page.dart:41 用 maybeSingle,null 時靠 try/catch 兜(comment 也說 "may not exist")— 應該明確處理 null vs errorclock_page.dart:72 _distance <= 100 — radius hardcodeboss_schedule_page.dart:19-23 _kHourStart = 8, _kHourEnd = 23 — 早班 7 點怎辦?boss_schedule_page.dart:446 budget = 40 / 240 — 「預算」寫死boss_schedule_page.dart:727 need = target_hours ?? 40empName[empName.length - 1](boss_schedule_page 多處)/ display_name[0](boss_staff_page)
→ 中日韓 surrogate pair 會炸(W12 dogfood crash 同種類)boss_schedule_page.dart:74 自建 channel,沒 hook HybridSyncManager(per memory feedback_realtime_channel_must_hook_reconnect.md,POS 學到的教訓)。
網路斷後 channel 死掉永遠不重連 → 老闆要 kill App 才能拿到即時排班。tip_settings_page.dart:318 程式碼自己承認有 bugboss_schedule_page.dart 2,171 行單檔。應拆 schedule_repository / template_repository / labor_law_gate / schedule_view / schedule_view_modelflutter analyze,建議 CI 加currentDate 規則)restaurant_members.role IN ('owner', 'admin')| 優先級 | 項目 | 檔案 | 預估工時 | 影響 |
|---|---|---|---|---|
| P0 | 修薪資 tab crash(.characters) | boss_payroll_page.dart | 已修 (0.2h) | 解 dogfood block |
| P0 | 排班公休判定改用 reservation_slots | boss_schedule_page.dart | 2h | 對齊 POS |
| P0 | 薪資 tab 讀 staff.base_salary + payroll_type 計算月薪 | boss_payroll_page.dart | 6h | 「薪資」tab 真有薪資 |
| P0 | tip_settings 讀 restaurant_settings.tip_system_enabled | tip_settings_page.dart | 1h | 消除員工困惑 |
| P0 | 排班 query 過濾 employment_status = 'active' | boss_schedule_page.dart, boss_staff_page.dart | 1h | 離職員工不要出現 |
| P1 | 請假衝突自動偵測 (排班看到請假 conflict) | boss_schedule_page.dart | 4h | 排班正確 |
| P1 | 打卡「當下狀態 banner」(上班中 / 下班中) | clock_page.dart | 2h | 員工有感 |
| P1 | 忘打下班偵測 cron + push 老闆 | 新 edge function | 4h | payroll 不算錯 |
| P1 | 排班「複製上週」按鈕 | boss_schedule_page.dart | 3h | 高頻 use case |
| P1 | 加班費 1.34x/1.67x 計算 + 勞健保預估 | boss_payroll_page.dart + 新 PayrollService | 8h | 真實月薪 |
| P1 | clock_page 4 層 refactor 示範 | 新 lib/clock/{gates,primitives,orchestrators}/ | 6h | 架構樣板 |
| P1 | geofence 半徑可調 | + restaurant_settings migration | 2h | 都市/郊區皆可用 |
| P1 | StringName helper(解 .characters 不安全 subscript) | 新 core/string_ext.dart + 5 處替換 | 1.5h | 未來不再 crash |
| P2 | 排班技能匹配 warning | boss_schedule_page.dart + LaborLawService → SchedulingGates | 5h | 排班智慧化 |
| P2 | shift_swap_requests 功能 | 新 swap_page.dart + orchestrator | 10h | 員工互換班 |
| P2 | 編輯 / 補打卡記錄 UI | 新 clock_admin_page.dart | 6h | 真實場景 |
| P2 | payslip PDF 產生 | 新 edge function + UI | 10h | 合規 |
| P2 | 合併 leave_policies + company_policies('leave') | boss_policy_page.dart + leave_page.dart | 4h | 消除重複 |
| P2 | 排班 realtime hook reconnect | boss_schedule_page.dart | 1.5h | 網路恢復後自動 reload |
| P2 | boss_schedule_page 拆檔(2171 → 4 個檔) | boss_schedule_page.dart | 8h | 可維護性 |
| P3 | NFC 打卡實作 | clock_page.dart + boss_clock_device_page.dart | 8h | 高級感 |
| P3 | 銀行匯款批次檔匯出 | 新 edge function | 6h | 對接會計 |
| P3 | 年度扣繳憑單 | 新 edge function + UI | 15h | 年度合規 |
| P3 | 政策版本歷史 + 員工簽收 | 新 policy_acknowledgements table + UI | 10h | 法律證據鏈 |
| P3 | 排班 AI 自動建議 | edge fn + UI | 20h+ | 差異化賣點 |
P0 合計:~10h(本週做完)· P1 合計:~31h(下週做完)· P2 合計:~44h(下下週起逐步)· P3 合計:~59h(defer 等正式用戶 feedback)
問:做到 B 還是 C?台灣餐飲業大部分老闆會接受 B 嗎?
問:你預期客戶餐廳會用哪一種?兩種都做還是 focus 一邊?
問:現在還沒上 production,要趁早拆 (C) 還是等用戶來再拆 (A)?
缺漏的 11 個排班功能裡,老闆覺得哪 3 個最重要?建議排序:
問:老闆會自己「複製上週」嗎?還是希望「AI 一鍵安排好」?
目前 company_policies('leave')(free-form 文字)跟 leave_policies(結構化 days_per_year / is_paid)是兩張表,
員工請假是讀後者,老闆設政策是讀前者 → 兩個地方各自編輯。
問:合併成一張 leave_policies(加 description 欄位),制度頁的「假別」改打 leave_policies?
問:制度頁的「薪資制度 / 加班規定」要影響系統行為,還是純文件?
台灣勞動法規(勞基法 §70)要求重大規定要員工簽收。若 ChefsMate 想做「合規賣點」可以加。 工時 ~10h,需要 push 通知 + 簽名畫板 + 新表。
問:這是「賣點」還是「nice-to-have」?P2 還是 P3?
| # | 老闆答 | 解讀 | 工時 |
|---|---|---|---|
| Q1 | C — 完整 payroll system | payslip PDF + 匯款檔 + 扣繳憑單 全做 | ~40h |
| Q2 | 兩種都做 | 店內裝置(boss_clock_device)+ 個人手機(clock_page)雙線強化 | ~12h |
| Q3 | B — 集中一週全拆 | 13 個 feature 全部 4 層架構 refactor | ~40h |
| Q4 | 跟錢有關的最重要,其他我安排 | 勞動成本 banner 最優先;其他依下方 wave 順序 | — |
| Q5 | 依照建議 | 合併 company_policies('leave') → leave_policies(加 description 欄) | ~3h |
| Q6 | 要影響系統行為 | 制度設計 → enforcement:薪資制度連 payroll / 加班規定連排班 / 試用期連 onboarding | ~15h |
| Q7 | 要員工簽收賣點 | policy_acknowledgements + push + 簽名畫板 — 合規賣點 | ~10h |
| # | 功能 | 跟錢相關? | 分到 Wave |
|---|---|---|---|
| 1 | 勞動成本 $ banner(讀 base_salary 算) | 💰 直接 | B.1(老闆指定 #1) |
| 2 | 請假衝突自動偵測 | — | B.2(dogfood block) |
| 3 | 「複製上週」 | — | B.4(高頻 UX) |
| 4 | 技能匹配 warning | — | B.5 |
| 5 | shift_swap 換班 | — | G.3 |
| 6 | 員工自助接班 | — | G.4 |
| 7 | 排班發布 push 通知 | — | G.5 |
| 8 | AI 排班建議(考慮人力成本) | 💰 間接 | H.1 |
| 9 | 員工偏好(preferred_shifts)自動考慮 | — | H.2 |
| 10 | 排班 PDF / 列印 | — | H.3 |
| 11 | 農曆假日疊加(影響加班費) | 💰 間接 | H.4 |
制度頁設了「時薪 200」→ payroll 自動讀(不能用 hardcode 預設)。這是 Q6 的「錢」部分。
13 個 feature 全拆 Gate / Primitive / Repository / Orchestrator。每個 feature 約 3h。 Wave B.3 clock_page 已示範樣板,其他 feature 對著樣板套。
| Wave | 內容 | 工時 |
|---|---|---|
| A | 緊急 P0 修 | ~6h |
| B | 錢相關核心 + clock 架構樣板 | ~15h |
| C | 完整 Payroll System | ~40h |
| E.2 | 薪資制度連動 | ~4h |
| D | 4 層架構 13 個 feature 大改造 | ~40h |
| E.1 + E.3 + E.4 | 其他 enforcement | ~11h |
| F | 員工簽收(合規賣點) | ~10h |
| G | 打卡兩派強化 + 排班 P2 | ~12h |
| H | nice-to-have | ~12h |
| 合計 | ~150h(約 4 週) | |
Timer.periodic 在 background 不 pause + setState 每秒整 page rebuild。修法見下表。flutter analyze 0 error / flutter build ios --no-codesign --debug ✓ Built Runner.app。| Wave | 內容 | 狀態 | 主要 commit |
|---|---|---|---|
| A | 緊急 P0(crash 修 / 營業時間對齊 / 離職員工濾掉 / tip gate / schema mismatch) | ✅ ship | Wave A 系列 |
| B | 錢相關核心(勞動成本 banner / 請假衝突 / 複製上週 / 技能 warning / clock 4 層樣板) | ✅ ship | Wave B 系列 |
| C | 完整 Payroll(底薪 / 加班費 / 勞健保 / 扣繳 / payslip PDF / 銀行匯款檔) | ✅ ship | payroll_calculator + payslip_pdf_service + bank_batch_exporter |
| D | 4 層架構大改造(先抓 clock 當樣板)— 後來在 Wave N + O 全推 | ✅ ship | N + O |
| E | 制度設計 enforcement(leave_policies / overtime_rules / benefit_policies) | ✅ ship | policy_templates + boss/policy/ |
| F | 員工簽收(policy_acknowledgements) | ✅ ship | employee/policy_ack/ |
| G | 打卡兩派強化(boss_clock_device + GPS geofence + IP whitelist + QR rotation) | ✅ ship | boss/clock_device/ + Wave M perf isolation |
| H | nice-to-have(shift_swap / notifications) | ✅ ship | features/employee/shift_swap + notifications_page |
| I | 設定模板化 + Stitch UI 大改造(25 industry templates、AlertDialog→Sheet) | ✅ ship | 60de6669 |
| J | 員工側 Stitch + 功能完善(my_payroll / leave_page / clock_page polish) | ✅ ship | 5c62933d |
| K | 老闆 page Stitch + AlertDialog→Sheet(boss_schedule / boss_staff / boss_payroll) | ✅ ship | 5c62933d |
| L | profile + auth Stitch(profile_page / login / register / role_selection) | ✅ ship | 5c62933d |
| M | 缺漏功能 + 全 app polish + 性能 bug fix(_RotatingQRCard isolated + AppLifecycle) | ✅ ship | bc57cbb4 |
| N | 4 層架構推進 — leave/ + staff/ 完整 Gate/Primitive/Repo/Orchestrator | ✅ ship | 1a2da4a4 |
| O | 4 層架構 100% — 剩 7 feature 全拆(schedule / station / clock_device / tip / profile / policy(employee) / auth) | ✅ ship | 5010a8c4 |
| Feature | 位置 | Audit 當下(Round 1) | Wave N 後 | Wave O 後 |
|---|---|---|---|---|
| clock(員工打卡) | features/employee/clock/ | ❌ | ✅ | ✅ |
| leave(請假) | features/employee/leave/ | ❌ | ✅ | ✅ |
| staff(老闆管員工) | features/boss/staff/ | ❌ | ✅ | ✅ |
| policy(老闆制度) | features/boss/policy/ | ❌ | ✅ | ✅ |
| payroll(老闆薪資) | features/boss/payroll/ | ❌ | ✅ | ✅ |
| schedule(老闆排班 3118 行) | features/boss/schedule/ | ❌ | ❌ | ✅ |
| station(站別 + 技能) | features/boss/station/ | ❌ | ❌ | ✅ |
| clock_device(老闆裝置) | features/boss/clock_device/ | ❌ | ❌ | ✅ |
| tip(員工小費) | features/profile/tip/ | ❌ | ❌ | ✅ |
| profile(個人資料) | features/profile/profile/ | ❌ | ❌ | ✅ |
| policy(員工查看 + 簽收) | features/employee/policy/ + policy_ack/ | ❌ | ❌ | ✅ |
| auth(login / register / role / create / join) | features/auth/_4layer/ | ❌ | ❌ | ✅ |
| shift_swap(換班) | features/employee/shift_swap/ | ❌ | ✅ | ✅ |
| 合計 | 0 / 13 = 0% | 6 / 13 = 46% | 13 / 13 = 100% |
boss_clock_device_page.dart 的 QR rotation ticker 用 Timer.periodic(Duration(seconds: 1)) + setState(...) → 每秒整個 page rebuild(裝置清單、geofence settings、QR widget 全部重畫)。Timer 在 app paused / hidden 時不會自動暫停 → 切到 background 還是每秒燒 CPU。clock_page 每 30 秒 status check),總體 CPU 燒到手機變慢。print/debugPrint release build 全被剝離 → 老闆看不到 log。boss_clock_device_page 內 nest _RotatingQRCard widget,只它自己 rebuild,parent page 完全不動。core/app_lifecycle.dart:WidgetsBindingObserver singleton,廣播 lifecycle 事件,page 訂閱在 paused/hidden cancel ticker、resumed 重啟。core/debug_log.dart:統一 AppLog.timer/channel/lifecycle/perf/query/warn(release build 仍可看),老闆抓 log 方便。AppLifecycle.instance.install()。bc57cbb4 · 結果: 切 background 後 ticker 直接停,前景 page 不再每秒 rebuild。
| P | 內容 | 原預估 | 實際 ship | 狀態 |
|---|---|---|---|---|
| P0 | 5 項緊急 fix(crash / 營業時間 / tip gate / 離職濾 / schema) | ~10h | Wave A 一輪打包 | ✅ |
| P1 | 排班 / 打卡 / 薪資核心 + clock_page 4 層樣板 | ~31h | Wave B + C(payroll 算薪 + payslip PDF + 匯款檔) | ✅ |
| P2 | 制度設計 enforcement + 員工簽收 | ~21h | Wave E + F | ✅ |
| P3 | 打卡強化(GPS / IP / QR) + nice-to-have | ~24h | Wave G + H + M | ✅ |
| 架構 | 4 層 refactor(Q3 原本選 C「只拆 P0/P1」) | ~40h(全拆) | Wave N + O(老闆改要全拆) | ✅ |
| UI | Stitch 全面化 + AlertDialog → Sheet | 未列 | Wave I + J + K + L | ✅ |
| Perf | 「手機變慢」修 | 未列 — dogfood 才抓到 | Wave M(_RotatingQRCard + AppLifecycle) | ✅ |
| Q | 題目 | 老闆答案 | 實際執行 |
|---|---|---|---|
| Q1 | 薪資 tab 要做到多深? | C(完整 payroll system) | ✅ Wave C ship payroll_calculator + payslip_pdf_service + bank_batch_exporter + withholding_statement_service |
| Q2 | 打卡是裝置還是員工手機為主? | 兩派都要 | ✅ Wave G — boss_clock_device + clock_page 並存,QR rotation + GPS geofence + IP whitelist |
| Q3 | 4 層 refactor 逐步還是一次性? | 原 B,後改全拆 A | ✅ Wave N + O 一次性 100% 全拆 |
| Q4 | 排班智慧化優先序? | 「跟錢有關優先」 | ✅ 勞動成本 banner / 違規偵測 / 技能 warning 都在 Wave B 排前 |
| Q5 | 制度設計 vs leave_policies 合併? | 合併,policy_templates 分類 | ✅ Wave E policy_templates.dart 統一 |
| Q6 | 制度設計純宣導還是 enforcement? | enforcement(leave 跑 leave_policies、overtime 跑 overtime_rules) | ✅ Wave E.2 連動 PayrollCalculator |
| Q7 | 員工簽收要不要做? | 要(合規賣點) | ✅ Wave F policy_acknowledgements + 簽名 + PDF |
AppLog tag 看出)| 項目 | 狀況 | 後續 |
|---|---|---|
| iOS 26+ simulator(M-series)+ mobile_scanner | GoogleMLKit 不支援 arm64 simulator — Wave G QR 掃描在 simulator 跑不起來,真機 OK | 等 mobile_scanner 上游修 |
| pre-existing lint(58 個 info/warning) | 主要是 use_build_context_synchronously + curly_braces_in_flow_control + unnecessary_cast | 後續 cleanup wave 一次補 |
features/auth/_4layer/ 命名 | 底線 prefix 避免跟既有 view file 撞 namespace,可後續 rename 成 auth/core/ | 後續 cleanup |
StaffsMate Swift 版(StaffsMate/) | 2026-02 已停止更新 — 任何 StaffsMate 改動只走 Flutter 版 | by design |
Audit 時間 2026-05-27 早 · 完工時間 2026-05-27 晚 · Codebase: staffsmate_flutter/ 8,656 行 → ~14,000 行 / 22 → 70+ 個 .dart 檔 / 20 個 Supabase 表