ADR 0013 — EventItem 執行中狀態碼 X→R + SP 端寫 完成時間¶
- 日期:2026-05-03
- 狀態:Accepted
- 決策者:使用者
Context¶
twork_skd_eventitem 透過 TWORK_SKD_UPDATE_EVENT_STATUS 預存程序更新狀態。原設計:
LP_P3 = 'X'表示「執行中」LP_P3 = 'Y'表示「完成」LP_P3 = 'F'表示「失敗」LP_P3 = ''表示「重設」
兩個問題:
'X'視覺上像cancel/failed:X在多數 UI 慣例代表 reject、close、cancel、wrong。掃 EventItem 列表時已完成 = 'X'容易被誤認成失敗或被取消的事件,但實際上是「正在跑」。F(Failed)已經佔走「失敗」語義,X跟F並列更混淆。完成時間欄位存在但沒人寫:Twork_skd_vw_eventitemModel.完成時間schema 早就有,但 SP 從沒寫過它;只有執行開始時間有 caller。事件結束後沒辦法回頭看「實際跑了多久」。
同時 AutoPilotHelper.ExecuteMethod 在本任務之前也根本沒呼叫過 'X'——只在成功且 執行後完成 == "Y" 時寫 'Y'。換句話說「執行中」只是設計上存在、production 從沒進入過這個狀態。執行開始時間 永遠是 NULL。
Decision¶
D1:把「執行中」代碼從 'X' 改為 'R'(Running)¶
R在電腦科學語境很明確(process running、systemd active(running)、Linux psRstate、git rebaseraction 之類),不會像X觸發失敗的視覺暗示。- 是字面變動而非語義變動:SP 的
LP_P3仍是任意varchar(1),DB schema 不需動。 - 已確認 production 端沒有任何 caller 現在寫
'X'(grep 過AutoPilotHelper、LogicBll/Calendar/、LogicBll/Workflow/、ViewModel/L.Communication/),只有測試文件LogicBllTests/Calendar/CalendarBllTests.cs把'X'當執行中描述。所以此變更無實際 breaking 客戶端,僅文件 + 測試需要同步換字面。
D2:完成時間 由 SP 端在 LP_P3='Y' 時自動寫 GETDATE()¶
兩個方案:
- A. SP 內 [完成時間] = CASE WHEN @LP_P3 = 'Y' THEN GETDATE() ELSE [完成時間] END
- B. C# 端 Update已完成 多收一個 completedAt 參數,再多塞一個 LP_P5
選 A。理由:
- 完成時間 永遠等於「SQL Server 收到 Y 的當下」——以 SQL Server 時鐘為準,不會被 client/server 時鐘漂移影響。
- 不增加 Update已完成 的方法簽章寬度;'Y' → 自動填 完成時間 是「狀態驅動行為」的副作用,符合 SP 內聚原則。
- R / F / '' 等其他狀態 explicit 寫 [完成時間] = [完成時間](保留原值),語義清楚不會誤覆蓋。
對稱地,執行開始時間 仍由 caller 端決定何時帶(AutoPilotHelper 進入 R 狀態時 DateTime.Now),因為「開始時間」需要在發 SQL 之前生(而不是 SQL 收到的當下),免得排隊延遲算進去。
D3:AutoPilotHelper.ExecuteMethod 補完整生命週期¶
[排程觸發]
→ bll.UpdateExecutionGuid(pkid, guid)
→ bll.Update已完成(pkid, "R", DateTime.Now) ← 標 R + 寫 執行開始時間
→ await task.Execute(...)
→ 成功 + 執行後完成=Y:bll.Update已完成(pkid, "Y") ← 標 Y,SP 自動寫 完成時間
→ 失敗:bll.Update已完成(pkid, "F") ← 標 F
理由:
- 之前 'R' 沒人寫,執行開始時間 / 完成時間 永遠 NULL,新檢視畫面(EventResultViewModel)的時間軸顯示永遠是空——空有 schema 沒有資料,等於沒做。
- 失敗後寫 'F' 是順便補的——之前也沒人標 F,事件失敗後跟「未執行」長一樣。
Consequences¶
正面:
- EventItem 列表掃過去能立刻分辨「正在跑(R)」「成功(Y)」「失敗(F)」「未跑(空)」,視覺不衝突。
- 完整時間軸(預定 → 開始 → 完成)有資料可看,未來做執行時間統計、超時告警都有基礎。
- SP 集中決定 完成時間 寫入時機,C# 端三狀態 caller 不必各自記得帶時間。
負面 / Migration:
- 既有 DB 中若有歷史 已完成 = 'X' 的 row(雖然根據 grep 結果應該沒有 production caller,但測試 / 人手測試可能殘留),需手動 UPDATE twork_skd_eventitem SET 已完成 = 'R' WHERE 已完成 = 'X' 一次性轉換,否則畫面上仍會看到 X。
- DB column 完成時間 對歷史資料一律 NULL,不會回填——只有本變更後新觸發的事件會有值。
測試 / 文件:
- LogicBllTests/Calendar/CalendarBllTests.cs 的 'X' 字面、已通知對應 type=2/已完成對應 type=1 的 assertion 已同步更新(後者是與最新 CalendarUpdateType enum mapping 對齊:已通知=1, 已完成=2,原測試是舊 mapping 的殘留)。
- docs/admin/scheduler.md 表格與範例 SQL 同步更新成 'R'。
Alternatives Considered¶
- (拒絕) 保留
'X',只新增「另一個執行中代碼」:兩個都代表執行中等於沒解決問題,反而更混亂。 - (拒絕)
完成時間改由 client 端Update已完成多帶一個參數:C# 端要記得在每個 caller 帶DateTime.Now,三狀態介面統一性更差。SP 內CASE WHEN更省事。 - (拒絕) SP 加狀態白名單
IF @LP_P3 NOT IN ('R','Y','F','') RAISERROR(...):行為變嚴格的 SP 升級風險高,舊 client(如有未掃到的)會直接斷線。先放鬆相容,未來若要嚴格化再開另一個 ticket。 - (拒絕) 用 enum 而不是 char:DB column 是
varchar(1),改成 enum 要 schema migration + 重寫所有 caller,CP 值不高;既有「char + ad-hoc 比對」夠用。
Migration¶
| 動作 | 對象 | 何時 |
|---|---|---|
| ALTER PROCEDURE 新版 SP | DB(user 端) | 本 commit 部署前 |
UPDATE ... SET 已完成 = 'R' WHERE 已完成 = 'X' |
DB(user 端,可選) | 部署 SP 後想清歷史殘留時 |
| 升級 client 程式 | TsERP / SchedulerWorker | 本 commit |