跳轉到

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 = '' 表示「重設」

兩個問題:

  1. 'X' 視覺上像 cancel / failedX 在多數 UI 慣例代表 reject、close、cancel、wrong。掃 EventItem 列表時 已完成 = 'X' 容易被誤認成失敗或被取消的事件,但實際上是「正在跑」。F(Failed)已經佔走「失敗」語義,XF 並列更混淆。
  2. 完成時間 欄位存在但沒人寫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 ps R state、git rebase r action 之類),不會像 X 觸發失敗的視覺暗示。
  • 是字面變動而非語義變動:SP 的 LP_P3 仍是任意 varchar(1),DB schema 不需動。
  • 已確認 production 端沒有任何 caller 現在寫 'X'(grep 過 AutoPilotHelperLogicBll/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

  1. (拒絕) 保留 'X',只新增「另一個執行中代碼」:兩個都代表執行中等於沒解決問題,反而更混亂。
  2. (拒絕) 完成時間 改由 client 端 Update已完成 多帶一個參數:C# 端要記得在每個 caller 帶 DateTime.Now,三狀態介面統一性更差。SP 內 CASE WHEN 更省事。
  3. (拒絕) SP 加狀態白名單 IF @LP_P3 NOT IN ('R','Y','F','') RAISERROR(...):行為變嚴格的 SP 升級風險高,舊 client(如有未掃到的)會直接斷線。先放鬆相容,未來若要嚴格化再開另一個 ticket。
  4. (拒絕) 用 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