跳轉到

排程作業(TsERP.SchedulerWorker)

TsERP 排程作業是一支獨立的 Windows Console 程式(TsERP.SchedulerWorker.exe),由 Windows 工作排程器每 5 分鐘觸發一次,負責行事曆事件 Email 通知AutoPilot 自動排程。本頁是給系統管理員 / DBA 看的部署與維運手冊。

功能簡介

舊版的 ERP 系統把這兩件工作放在 LogicBll.Timer.TimerBll 內的 DispatcherTimer只有當有人開著 ERP 主程式時才會跑。實務上這帶來幾個痛點:

  • 下班後沒人開 ERP,行事曆通知信不會寄出
  • AutoPilot 自動排程必須仰賴某個使用者「掛著」ERP
  • 多人同時開 ERP 時,每台都會跑同一份排程,造成重複通知

新版把排程邏輯抽成獨立 TsERP.SchedulerWorker.exe,部署到 server,由 Windows 工作排程器(schtasks)每 5 分鐘觸發一次。優點:

  • 不需要使用者開 ERP,server 上自動跑
  • 單一執行點,靠 Mutex 保證不重疊
  • 可獨立升級,與 ERP 主程式解耦
  • 失敗會留下 'F' 狀態 + log,方便管理員追查

與舊版 TimerBll 的差異

舊版 ERP 主程式仍保留 TimerBll 相關程式碼,但 MainWindowBtnTimerViewModel 已不再實際啟動該 timer(避免與本排程作業衝突)。請務必確認 server 上只有排程作業在跑,使用者端 ERP 不會重複觸發通知。

排程 proc 與資料表位置(ADR 0065)

自 2026-06-28 起(NOGI),排程/自動化的預存程序與其產出的資料表集中在 集中自動化庫 darbnogiauto1,不再放公司庫 darbnogicsa1

物件 位置
TWORK_SKD_GET_PENDING_NOTIFY / _AUTOEXEC darbnogiauto1(讀 csa 行事曆 view,三段式)
TWORK_SKD_MAILQUEUE 表 / _DEQUEUE / _UPDATE darbnogiauto1
TWORK_AUTO_SAFETYSTOCK + twork_auto_safetystockhistory darbnogiauto1(讀 csa 庫存,三段式)
DARB_GETDATA4(結果檢視用) darbnogiauto1(另保留 csa 既有版)
TWORK_SKD_GETEVENT / TWORK_SKD_UPDATE_EVENT_STATUS 仍在 darbnogicsa1(行事曆 UI 共用)

執行不再走共用派發器,而是 log 庫的排程專屬派發器 DARB_LOG_SYS_SKD_EXEC(稽核寫 darbnogilog1.dbo.log_sys_skd、目標一律導 darbnogiauto1)。

  • 部署:在目標 SQL 執行個體跑 sql/deploy-darbnogiauto1.sqlsqlcmd -f 65001 或 SSMS)。
  • 安全庫存:行事曆事件 參數 需設 Dispatcher=SKDProcedure=TWORK_AUTO_SAFETYSTOCK結果表=twork_auto_safetystockhistory,「執行結果」檢視才會經 SKD 派發器讀新庫。
  • 稽核查詢:排程歷程看 darbnogilog1.dbo.log_sys_skd(與主流程 log_sys、AI 的 log_sys_ai 分離)。
  • 多客戶:其他客戶需各自 darbXXauto1 變體+在該客戶 log 庫佈 DARB_LOG_SYS_SKD_EXEC/log_sys_skd

詳見 ADR 0065

前置需求

項目 需求
作業系統 Windows Server 2019 / 2022(或 Windows 10 / 11 Pro)
.NET Runtime .NET 8 Desktop Runtime(x64)
SQL Server 可從排程主機連線到 ERP 各公司資料庫
Mailgun 可從排程主機對外發出 HTTPS request 到 Mailgun API
DatabaseList.xml 路徑 c:/TEMPS/SqlLocation/DatabaseList.xml,包含目標 DB 連線資訊
服務帳號 一個專用本機 / 網域帳號,能讀 XML、寫 log、連 SQL、連網

資料庫變更(DBA 必做)

排程作業靠兩支單一職責 SP 撈出待處理事件——dbo.TWORK_SKD_GET_PENDING_NOTIFY(待 Email 通知)與 dbo.TWORK_SKD_GET_PENDING_AUTOEXEC(待 AutoPilot 自動執行)。需要 DBA 先在每個目標公司資料庫做下列改動。

歷史對照

這兩支 SP 取代了原 TWORK_SKD_NOTIFYEVENT@QueryType=1 / =2 兩個分支),詳細決策見 ADR 0015。舊 SP 仍可保留 30 天再 drop。

1. 行事曆 base table 加欄位

行事曆事件的底層 table(即 Twork_skd_vw_eventitem view 對應的 base table)需要新增「執行開始時間」欄位,作為殭屍判定基準。

-- 範例 DDL,實際 table 名請依貴公司 schema 調整
ALTER TABLE [dbo].[行事曆事件 base table]
    ADD [執行開始時間] datetime NULL;
GO

欄位型別必須是 datetime

CalendarBll.Update已通知 / Update已完成 多載會把 DateTime.Now 透過 yyyy/MM/dd HH:mm:ss 格式字串傳給 SP,欄位型別必須能容納此值。

2. 建立兩支撈待辦事件 SP

完整 DDL 在 SqlBI/TWORK_SKD_GET_PENDING_NOTIFY.sqlSqlBI/TWORK_SKD_GET_PENDING_AUTOEXEC.sql,DBA 直接執行即可。摘要:

TWORK_SKD_GET_PENDING_NOTIFY(@datetime) — 待 Email 通知

CREATE PROCEDURE [dbo].[TWORK_SKD_GET_PENDING_NOTIFY]
    (@datetime datetime)
AS
BEGIN
    SET NOCOUNT ON;
    SELECT *
    FROM dbo.twork_vw_calendarevent
    WHERE 已審核 = 'Y'
      AND 是否停用 <> 'Y'
      AND 通知 = 'Y'
      AND 通知帳號 <> ''
      AND 已通知 <> 'Y'
      AND @datetime >= 通知時間;
END

TWORK_SKD_GET_PENDING_AUTOEXEC(@datetime) — 待 AutoPilot 自動執行

CREATE PROCEDURE [dbo].[TWORK_SKD_GET_PENDING_AUTOEXEC]
    (@datetime datetime)
AS
BEGIN
    SET NOCOUNT ON;
    SELECT TOP 20 *
    FROM twork_vw_calendarevent
    WHERE 已完成 NOT IN ('Y', 'F')
      AND (已完成 <> 'R' OR 執行開始時間 < DATEADD(MINUTE, -10, @datetime))
      AND 已審核 = 'Y'
      AND 是否停用 <> 'Y'
      AND 是否自動執行 = 'Y'
    ORDER BY 預定時間 ASC;
END

關鍵點:

  • TOP 20 對應 Scheduler.BatchSize,每次最多 20 筆
  • ORDER BY 預定時間 ASC 依時間先進先出
  • 已完成 NOT IN ('Y','F'):已完成的 Y 與失敗的 F 都不再撈
  • 殭屍回收條件 (已完成 <> 'R' OR 執行開始時間 < DATEADD(MINUTE, -10, @datetime)):非執行中(不是 R),或雖是 R 但 10 分鐘前就開始 → 視為前一輪 crash 重撈
  • @datetime 是殭屍判定基準時間,由 caller 傳當下時間,便於外部測試

3. 新增 SP dbo.TWORK_SKD_UPDATE_EVENT_STATUS

CalendarBll.Update已通知(pkid, status, time)Update已完成(pkid, status, time) 多載會呼叫這支 SP 一次更新「狀態 + 執行開始時間」。請建立:

參數 對應 C# 端 說明
LP_P1 type '1'=已通知、'2'=已完成
LP_P2 pkid 事件主鍵
LP_P3 status 'R'=執行中(Running) / 'Y'=完成 / 'F'=失敗 / ''=重設
LP_P4 timestamp yyyy/MM/dd HH:mm:ss 字串;空字串代表不更新時間

LP_P1='2'(已完成分支)且 LP_P3='Y' 時,SP 會自動把 [完成時間] 設為 GETDATE();其他狀態(R/F/'')不動 完成時間

CREATE PROCEDURE [dbo].[TWORK_SKD_UPDATE_EVENT_STATUS]
    @LP_P1 varchar(1),    -- 1 = 已通知, 2 = 已完成
    @LP_P2 varchar(50),   -- pkid
    @LP_P3 varchar(1),    -- 'R' / 'Y' / 'F' / ''
    @LP_P4 varchar(20)    -- yyyy/MM/dd HH:mm:ss 或 ''
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @StartTime datetime = NULL;
    IF @LP_P4 <> ''
        SET @StartTime = CONVERT(datetime, @LP_P4, 111);

    IF @LP_P1 = '1'
        UPDATE [行事曆事件 base table]
           SET [已通知] = @LP_P3,
               [執行開始時間] = CASE WHEN @LP_P4 = '' THEN [執行開始時間] ELSE @StartTime END
         WHERE [pkid] = @LP_P2;
    ELSE IF @LP_P1 = '2'
        UPDATE [行事曆事件 base table]
           SET [已完成] = @LP_P3,
               [執行開始時間] = CASE WHEN @LP_P4 = '' THEN [執行開始時間] ELSE @StartTime END,
               [完成時間] = CASE WHEN @LP_P3 = 'Y' THEN GETDATE() ELSE [完成時間] END,
               [是否停用] = CASE WHEN @LP_P3 = 'Y' THEN 'Y' ELSE '' END
         WHERE [pkid] = @LP_P2;
END
GO

SP 名稱

請依貴公司命名慣例調整 TWORK_SKD_UPDATE_EVENT_STATUS。Common 端是用 Procedure.TWORK_SKD_UPDATE_EVENT_STATUS.Name 取的常數,DBA 改完 SP 後若名稱有異,請同步通知開發端調整 Procedure 列舉。

部署步驟

1. 發佈 Console 程式

在開發機把 TsERP.SchedulerWorker 專案 publish 為 framework-dependent:

dotnet publish TsERP.SchedulerWorker/TsERP.SchedulerWorker.csproj `
    -c Release `
    -r win-x64 `
    --self-contained false `
    -o C:\Publish\Scheduler

或 self-contained(檔案大但不需在 server 裝 .NET):

dotnet publish TsERP.SchedulerWorker/TsERP.SchedulerWorker.csproj `
    -c Release `
    -r win-x64 `
    --self-contained true `
    -o C:\Publish\Scheduler

2. 複製到 server

C:\Publish\Scheduler\ 整個資料夾複製到 server,建議放:

C:\ERPDeploy\Scheduler\
├── TsERP.SchedulerWorker.exe
├── TsERP.SchedulerWorker.dll
├── appsettings.json
└── ...其他依賴 dll...

3. 建立 log 目錄

排程作業透過 SchedulerLog 把所有歷程(INFO 與 ERROR)寫到 C:\temps\scheduler\yyyy-MM-dd.txt,每天一個檔。首次執行時若資料夾不存在會自動建立,但建議事先建好並設好權限:

New-Item -Path "C:\temps\scheduler" -ItemType Directory -Force
icacls "C:\temps\scheduler" /grant "<服務帳號>:(OI)(CI)M"

指令說明

New-Item

參數 意思
-Path "C:\temps\scheduler" 建在這個路徑
-ItemType Directory 建的是資料夾(不是檔案)
-Force 父資料夾 C:\temps\ 不存在時自動建出來;目標已存在時不報錯

icacls:Windows 內建的 ACL(檔案權限)編輯工具。

參數 意思
/grant 授予權限(不是拒絕、不是取代既有權限)
<服務帳號> 佔位符,替換成實際帳號(見下方範例)
(OI) Object Inherit — 套用到資料夾內所有檔案
(CI) Container Inherit — 套用到所有子資料夾
M Modify — 可讀 / 可寫 / 可刪檔,不能改權限或拿走所有權

服務帳號範例

依你工作排程器的「執行此工作時,請使用下列使用者帳戶」設定替換 <服務帳號>

# 本機專屬服務帳號(手冊推薦做法)
icacls "C:\temps\scheduler" /grant "tsERPSched:(OI)(CI)M"

# 用 SYSTEM 帳號跑
icacls "C:\temps\scheduler" /grant "SYSTEM:(OI)(CI)M"

# 網域帳號(公司網域 DARBWARE,使用者 erpsvc)
icacls "C:\temps\scheduler" /grant "DARBWARE\erpsvc:(OI)(CI)M"

不知道目前用哪個帳號?開「工作排程器」 → 找到 TsERP Scheduler → 點選 → 看下方「一般」分頁的「執行此工作時,請使用下列使用者帳戶」欄位。

驗證

icacls "C:\temps\scheduler"

輸出應包含類似 tsERPSched:(OI)(CI)(M) 的行,代表已套用成功。

什麼情況可以省略 icacls

若排程是用 Administrator 或具有同等權限的帳號跑(例如個人開發機自己當管理員),對 C:\temps\ 預設就有完整權限,第二條可跳過。正式環境若用低權限服務帳號則必設 — 不設會導致 SchedulerLog 寫檔失敗(但因為 try/catch 吞例外,會靜默失敗,磁碟上完全看不到 log 檔)。

4. 確認 DatabaseList.xml

排程作業共用 ERP 主程式的 SQL 連線設定,會讀取:

c:/TEMPS/SqlLocation/DatabaseList.xml

請確認此檔案於 server 存在,且含有 appsettings.jsonScheduler.Databases[*].Name 對應的 <DataBase> 條目。

5. 編輯 appsettings.json

打開 C:\ERPDeploy\Scheduler\appsettings.json,依環境調整(詳見配置檔說明)。

6. 手動跑一次驗證

以服務帳號身份開啟 PowerShell:

cd C:\ERPDeploy\Scheduler
.\TsERP.SchedulerWorker.exe

預期 console 輸出:

[SchedulerJob] === DB darbcylog1 on TWORKSQL ===
[SchedulerJob] Notify: no rows.
[SchedulerJob] AutoPilot: no rows.
[Scheduler] Done. exitCode=0

如果出現 Fatal error 或 exitCode 為 2,請先處理疑難排解再進入下一步。

服務帳號設定

建議不要Administrator 或一般使用者帳號跑排程作業,請建立專屬本機帳號:

1. 建立本機帳號

$pw = Read-Host -AsSecureString "Set password"
New-LocalUser -Name "tsERPSched" -Password $pw -PasswordNeverExpires -UserMayNotChangePassword

或在「電腦管理 → 本機使用者及群組 → 使用者」手動新增。

2. 賦予最小權限

權限 設定方式
以批次工作登入 (SeBatchLogonRight) 本機安全性原則 → 使用者權限指派 → 「以批次工作登入」加入 tsERPSched
讀取 c:\TEMPS\SqlLocation\DatabaseList.xml 在檔案內容 → 安全性 → 加入 tsERPSched 給「讀取」
修改 C:\ERPDeploy\Scheduler\ 同上,給「修改」
修改 C:\temps\scheduler\ SchedulerLog 寫入路徑(每日一檔)
SQL Server 連線 在 SQL Server 加入 Login,授予 ERP 各公司資料庫對應 SP 的 EXECUTE 權限
網路 確認防火牆允許對 api.mailgun.net HTTPS(443)出站連線

無互動登入即可

服務帳號不需要「允許本機登入」權限,建議於本機安全性原則「拒絕從本機登入」加入此帳號,降低被當作互動帳號濫用的風險。

Windows 工作排程器設定

1. 建立排程

開啟「工作排程器」 → 「建立工作」(不是簡單建立工作):

一般

  • 名稱TsERP Scheduler
  • 使用者帳戶tsERPSched
  • 不論使用者是否登入皆執行:勾
  • 以最高權限執行:依需求(通常不需勾)
  • 設定:Windows Server 2019 / 2022 / 11

觸發程序

  • 新增觸發程序,每日
  • 開始:選擇生效日,時間 00:00:00
  • 重複工作每:5 分鐘
  • 持續時間:無限期
  • 已啟用:勾

動作

  • 動作:啟動程式
  • 程式或指令碼:C:\ERPDeploy\Scheduler\TsERP.SchedulerWorker.exe
  • 開始於(選擇性):C:\ERPDeploy\Scheduler\

條件

  • 只有在電腦使用 AC 電源時才啟動工作:取消勾選(server 不需要)
  • 只有在下列網路連線可用時才啟動:依環境

設定

選項 建議值
允許隨選執行此工作
如果排定的開始時間錯過則盡快執行此工作
如果工作失敗,每隔重新啟動 不勾(讓下一個 5 分鐘自然觸發即可)
停止超過下列時間的工作 勾,4 分鐘(hard timeout,防止某次 hang 卡住下次觸發)
如果執行中的工作未在要求時結束,將強制停止
如果工作未排定再次執行則在以下時間之後刪除 不勾
如果工作已在執行則使用下列規則 不要啟動新的執行個體

務必設「不要啟動新的執行個體」

雖然程式內部已有 Mutex 保護,但工作排程器層級也要設成「不重疊」,避免同時噴出多個 process 競爭 log 檔案 handle。

2. 用 schtasks CLI 建立(替代方式)

schtasks /Create ^
    /TN "TsERP Scheduler" ^
    /TR "C:\ERPDeploy\Scheduler\TsERP.SchedulerWorker.exe" ^
    /SC MINUTE /MO 5 ^
    /RU "tsERPSched" /RP "***" ^
    /RL LIMITED ^
    /F

CLI 設定完仍建議在 GUI 確認「設定」分頁的「不要啟動新的執行個體」與「4 分鐘 timeout」是否正確。

配置檔說明

appsettings.json 結構:

{
  "MailGun": {
    "ApiKey": "<your-mailgun-api-key>",
    "Domain": "mg.darbware.com",
    "Sender": "@\"TWork ERP Mail Service <tworkservice@mg.darbware.com>\""
  },
  "Scheduler": {
    "Databases": [
      { "Name": "darbzdcsa1", "ConnectionString": "" }
    ],
    "AlertRecipients": ["admin@example.com"],
    "ZombieTimeoutMinutes": 10,
    "BatchSize": 20,
    "DefaultSrvdbid": 1
  }
}

MailGun 區段

欄位 說明
ApiKey Mailgun API Key(私鑰,請勿外流)
Domain Mailgun 域名
Sender 寄件人字串,含格式化引號

Scheduler 區段

欄位 必填 預設 說明
Databases [] 要處理的公司資料庫清單,留空陣列代表跑 DatabaseList.xml 內全部 DBName 對應 XML 內 <DataBase> 名稱,須完全一致(不分大小寫)
Databases[*].ConnectionString "" 預留欄位;目前實際連線仍以 DatabaseList.xml 為準,這裡填了不會生效
AlertRecipients [] 致命錯誤時的告警收件人(保留欄位,目前由 SchedulerLog 處理;未來可擴充用)
ZombieTimeoutMinutes 10 殭屍回收門檻(分鐘)。注意:實際判定寫死在 SP 內的 DATEADD(MINUTE, -10, ...),此欄位目前是給 SP 端參考的契約值,調動時請同步改 SP
BatchSize 20 每次最多處理筆數。注意:實際 TOP 20 寫死在 SP 內,調動時請同步改 SP
DefaultSrvdbid 1 SchedulerUser.Srvdbid 預設值,影響 LoggingId / log 路由

ZombieTimeoutMinutes / BatchSize 是契約值

這兩個欄位目前不會主動傳到 SQL 端,SP 內的 TOP 20DATEADD(MINUTE, -10) 是寫死的。把它放在 config 是為了讓管理員一眼看到「殭屍 = 10 分鐘」「批次 = 20 筆」這兩個約定,真正要改值請改 SP

多公司支援

跑單一公司

"Databases": [
  { "Name": "darbcylog1" }
]

跑多公司(依序,同一 process 內逐個切 connection)

"Databases": [
  { "Name": "darbcylog1" },
  { "Name": "darbzdcsa1" },
  { "Name": "darbtest" }
]

排程作業會依序處理每個 DB(呼叫 IDatabaseConnection.ChangeParameter),每個 DB 各自跑一輪 Notify + AutoPilot。某個 DB 失敗不影響其他 DB。

跑全部 DB

"Databases": []

留空陣列時,排程作業會把 DatabaseList.xml 內的所有 DB 都掃過一遍。

多公司 + 4 分鐘 timeout 的取捨

如果有 5 個以上公司、每家都有大量待處理事件,5 分鐘觸發 + 4 分鐘 hard timeout 可能不夠。建議:

  • 拆成多支排程作業(複製目錄、各自 appsettings.json 跑不同 DB)
  • 或調高 BatchSize 並把 timeout 拉到 8 分鐘,但觸發改成每 10 分鐘

警示通知

自 2026-07-13 起,排程作業除了行事曆 Email 通知與安全庫存警示外,再新增三種主動警示信:應收逾期、票據到期、金流預測跌破警戒。它們與安全庫存警示一樣,共用既有的 Mailgun 通道與 twork_skd_mailqueue 郵件佇列——警示源只負責「判定 + 組信 + 入列」,實際寄送仍統一回排程作業。門檻、收件人、啟用開關由使用者端的警示規則維護頁twork_sys_alertrule 表)設定,DBA 只需部署 SP/表並掛好排程事件。

規則設定 vs 排程掛法

「發不發、發給誰、門檻多少」由業務端在警示規則維護設定;「什麼時候跑」由本節的排程事件觸發。兩者缺一不可:規則啟用但沒掛排程事件不會發,掛了事件但規則未啟用也不會發。

資料庫部署(DBA 必做)

物件 位置 部署腳本
twork_sys_alertrule(警示規則表) 各客戶公司庫(csa) sql\alertrule-deploy.sql
twork_auto_alertlog(抑制指紋紀錄表) 集中自動化庫(auto) sql\alertlog-deploy.sql
TWORK_AUTO_AR_OVERDUE(應收逾期 SP) auto 庫 sql\ar-overdue-deploy.sql
TWORK_AUTO_CHECK_DUE(票據到期 SP) auto 庫 sql\check-due-deploy.sql

正式機 proc_param 為 IDENTITY

部署腳本檔頭已註明兩環境差異:正式機 proc_param 為 IDENTITY,請用不帶 pkid 的版本。跑 sqlcmd 時務必加 -f 65001(含中文),且注意 QUOTED_IDENTIFIER 需為 ON(預設 OFF 建含 JSON 索引的 SP/DML 會炸 Msg 1934),部署後實查物件存在再收工。

三支警示怎麼掛排程

警示與安全庫存一樣透過行事曆事件觸發,依警示源型態分兩種掛法:

SP 型:應收逾期、票據到期(走 SKD 派發器,零 C#)

在行事曆事件的「參數」欄設定,比照安全庫存慣例:

警示 參數設定
應收逾期 Dispatcher=SKDProcedure=TWORK_AUTO_AR_OVERDUE
票據到期 Dispatcher=SKDProcedure=TWORK_AUTO_CHECK_DUE

事件到觸發時間後,經 log 庫的排程專屬派發器 DARB_LOG_SYS_SKD_EXEC 執行對應 SP,SP 內部自行讀規則表門檻、判定、組信、入列。

C# 型:金流預測跌破警戒(走 AutoPilot task)

金流跌破的判定重用金流預測的 C# 純函數(不搬進 SQL),因此走 AutoPilot 而非 SKD 派發器。在行事曆事件設定為自動執行、對應 AutoPilot 動作 Flowcast_BaselineAlert;task 內部讀規則表的 警戒線 門檻、比對金流預測累計淨額,未被抑制時經 MailQueueEnqueuer 入列。

金流跌破警示 runtime 尚未驗收

Flowcast_BaselineAlert 的排程實跑(需登入 context 的行事曆事件觸發)截至 2026-07-13 尚未完整 runtime 驗收,正式啟用前請先在測試事件實跑一輪確認。

抑制視窗行為(避免同一警示轟炸)

三支警示在入列前都會對「警示內容」計算指紋(twork_auto_alertlog),比對抑制視窗(規則表 抑制視窗小時,預設 24h)內的近期紀錄:

  • 同內容、視窗內已發過 → 靜音,不重寄
  • 內容有變(逾期名單/票據清單增減)→ 指紋改變 → 照常再通知
  • 金流跌破只通知首次跌破:指紋維度 =(首次跌破月, 警戒線);只要這兩者沒變就不重寄,跌破月移動或警戒線調整才會再發

缺 auto 庫的靜默 no-rows

SKD 派發一律導向對應的 auto 庫。若某客戶 log 庫缺對應 auto 庫,派發會靜默回無資料(no-rows)不報錯——多客戶部署時請逐一確認 auto 庫與 SP 都已建立(可 EXEC 一次 SP 看有無執行紀錄)。

詳見 ADR 0092。收件人兩層 fallback(規則表有值用之、否則退回事件「通知帳號」)、通道限制(僅 Email)與規則欄位意義見警示規則維護頁

排程報表自動寄送(ScheduledDeck)

自 2026-07-13 起,排程作業可依 CUSTOMDASH 自訂查詢配置無人值守產出報表並以附件寄出。走 AutoPilot taskCustomdash_ScheduledDeck):到觸發時間 → 子行程 headless 產檔(EXCEL/HTML/HTMLMOBILE/PPT)→ 經 SKD 派發器入列 twork_skd_mailqueue(附件多檔以 ; 串接)→ 同鏈 Mailgun 寄出;行動版另可推上受控網址(見發布到受控網址)。

怎麼掛排程

在行事曆事件設定為自動執行單據別 = CUSTOMDASH執行方法 = ScheduledDeck,並在「參數」填 JSON:

參數 必填 說明
配置pkid 要產出的 CUSTOMDASH 配置主鍵
格式 EXCEL,HTML,HTMLMOBILE,PPT 多選逗號分隔(預設 EXCEL,HTMLHTMLMOBILE=手機直立版、別名 MOBILE,見行動版格式;PDF 目前不支援)
收件人 Email,逗號分隔
輸出目錄 預設 C:\temps\排程報表\;產出檔留存供稽核
月份 空=當期;支援 {-d1}(上月)或 yyyy/MMyyyy/MM/dd
保留天數 預設 30;每次寄送成功後清該目錄下本配置超過天數的舊產出檔(≤0=不清理)
發布網址 預設空=不發布;設 Y 時寄信成功後把最新行動版推上受控網址(見發布到受控網址),主管手機開網址即看

事件的「參數顯示」會帶出人話,例:排程報表:配置#3(當期)→ 收件人 2 人,格式 EXCEL,HTML

六個是事件參數、使用者代號是登入帳號

上表六欄是掛在行事曆事件上的參數 JSON。「使用者代號」不在事件參數裡——它是排程作業產檔時要用的 headless 登入帳號,設在 worker 設定檔(見下方headless 登入帳號),全公司共用一組、不隨事件個別設定。

月報每月自動產上個月報表

把事件觸發時間設在每月 1 號、月份{-d1},排程作業每月 1 號會自動產出「上個月」的報表寄出——月報從此無人值守。{-d1}BuildScheduledParameter 於觸發當下展開成實際年月。

產出檔名含期間段

自 2026-07-14 起檔名加入期間段<配置名>_<期間>_<產出日>(期間=目標月 yyyyMM 或「當期」),行動版再加尾碼 _mobile,例 財務報表_202606_20260714_mobile.html。原因:同配置的日報(當期)與週報(上月)若同日產出,舊命名會互相覆蓋。保留天數清理同時認得新舊兩種檔名,既有舊檔照常清理。

保留天數清理的安全界線

清理刪符合本功能命名模式(<配置名>_<期間>_yyyyMMdd[_mobile].xlsx/.html/.pptx)且屬該配置的過期檔,目錄下任何其他檔案一律不碰;且只在寄送入列成功之後執行,逐檔失敗記 log 不影響主流程。

PDF 格式本期未支援

格式 可填 EXCELHTMLHTMLMOBILEPPT(可多選逗號分隔)。PDF 目前不支援——指定 PDF 會記一筆警告後略過,不會中止其他格式。需要 PDF 請改用面板內的「彙整列印」互動操作。

行動版格式(手機直立閱讀)

格式 加入 HTMLMOBILE(別名 MOBILE)後,排程作業會額外產出一份專為手機直立畫面設計的 HTML,讓主管不必開電腦、直接在手機上看報表。它與桌機版 HTML 各自獨立,兩者可同時勾選並存、互不影響。

產出檔名:桌機版 HTML 的檔名尾加上 _mobile,例:財務報表_202606_20260714_mobile.html(期間段規則見上方「產出檔名含期間段」)。

信件裡怎麼認:信件內文會把行動版附件明確標為「(行動版)」,並提示「請用手機瀏覽器開啟」;桌機版附件則不加標記。主管在手機收件匣直接點行動版附件即可。

手機上的閱讀體驗

  • 直立卡片流:內容改成由上而下、一張張卡片堆疊,不再是桌機版那種需要左右縮放的寬版面。
  • 關鍵指標(KPI)置頂:最重要的數字放在最上方,一開啟就看得到。
  • 表格可橫向捲動:欄位多的明細表在手機上以橫捲呈現,不會被壓扁到看不清。
  • 可加到主畫面:用手機瀏覽器開啟後,可「加入主畫面」,之後像 App 一樣一點就開。

行動版會隱藏零值,桌機版不變

為了讓手機小螢幕聚焦在有數字的資料上,行動版只在此格式做兩件事:

  • 整列全部為 0 的資料列不顯示(例如當月完全沒有異動的科目就不佔版面)。
  • 數值欄的 0 顯示為空白(右對齊的金額/數量欄),讓非零數字更醒目。

代號類欄位(如「0001」這種左對齊代號)不受影響、照常顯示。桌機版 HTMLEXCEL 維持原樣、不隱藏任何列或值。

行動版是「快照」,不是即時更新

附件與受控網址上的內容都是排程當下產出的快照,反映的是那個時間點的資料,開啟後不會自動更新。要看更新後的數字,需等下一次排程產出(頻率由行事曆事件的觸發時間決定),或改在電腦上開 ERP 面板即時查詢。

發布到受控網址(選用)

除了寄附件(人人收件匣各一份),排程還能把最新一份行動版推到一個受控網址,主管手機開網址即看、不必翻信找附件。啟用方式:在行事曆事件的「參數」把 發布網址 設為 Y(預設空=不發布)。

啟用後的效果

  • 寄信成功後,排程作業會自動把該次產出的最新行動版推上受控網址;主管手機開同一個網址永遠看到最新一份。
  • 網址可「加入主畫面」,之後一點就開,體驗接近 App。
  • 內容同樣是快照(見上方警告),非即時。

前置需求:需 IT 先完成一次性設定

受控網址由 IT 以 Cloudflare Access(Email 白名單驗證)保護——只有白名單內的 Email、通過驗證後才進得去,不是公開網頁。首次啟用前,需 IT 完成 Cloudflare Access 政策與 worker 環境變數設定(含一項「已確認 Access 生效」的明示旗標)。這屬 IT 部署範疇,細節見 IT 部署文件,管理員這邊只要把 發布網址Y 即可。

安全閘:IT 未確認完成前,系統會拒絕上傳真實數據

為避免財報在未受保護的狀態下外流,系統採技術閘把關:IT 未明示確認「Access 已啟用且驗證有效」之前,帶真實數據的行動版一律拒絕上傳(只會記 log,不會硬送出去)。因此若 發布網址=Y 但網址上遲遲沒有最新內容,多半是 IT 端的確認尚未完成——請聯繫 IT,而不是反覆重掛排程。

發布失敗不影響寄信

發布到受控網址與寄信是兩條獨立的路:就算發布失敗(例如網路暫時不通、IT 設定未就緒),也只會記 log,不會影響附件正常寄出。主管仍收得到信件附件。

headless 登入帳號(使用者代號)

排程作業要在無人登入的情況下把配置整頁跑一遍、產出檔案,需要一組 headless 登入帳號(使用者代號+密碼)。這組帳號設在 worker 設定檔 appsettings.jsonScheduledDeck(與 ERP 使用者登入同一組帳號體系):

{
  "ScheduledDeck": {
    "User": "",
    "Password": ""
  }
}
欄位 說明
User headless 登入用的使用者代號(同 ERP 登入帳號);排程作業以此身份跑查詢與匯出
Password 對應密碼

帳密嚴禁寫進版控的 appsettings.json

原始 appsettings.jsonUserPassword 一律留空,帳密只在部署現場覆寫:本機以 appsettings.local.json(gitignored)或 %ProgramData% 設定檔覆寫,正式環境建議走 Azure Key Vault。憑證未設定(User/Password 為空)時,排程報表任務會明確失敗並記 log,不會 fallback 到任何寫死帳密

使用者代號決定看得到哪些配置

排程作業以這組帳號的身份載入 CUSTOMDASH 配置,因此該帳號要對目標配置有可見權限(配置的分享可見範圍見自訂查詢面板的「儲存/載入設置」)。建議用一個具足夠可見範圍的專用帳號。

方法字典登錄(選用)

排程能執行不靠字典——引擎用 單據別 + 執行方法 反查程式 AutoPilotRegistryScheduledDeck 已內建註冊)。字典僅為自動執行方法維護畫面的 discoverability:

  • 顯示名(必要,已內建):中文名走多語系字典 AUTOPILOTMETHOD.ScheduledDeckCommon/Lang/Xaml/AutopilotmethodMethod.json),排程/事件畫面即顯示「排程報表自動寄送」。
  • 字典列(選用):若要在「自動執行方法」表單看到此方法,於各公司庫sql/autopilotmethod-register-deploy.sql(冪等、僅在該庫已建 twork_sys_autopilotmethod 時登錄;含中文須 sqlcmd -f 65001,部署後實查該列)。

DBA 需部署的物件

正式環境需在對應資料庫執行下列四支腳本(含中文一律 sqlcmd -f 65001,部署後實查物件存在再收工):

部署腳本 位置 用途
sql/mailqueue-attachment-deploy.sql 各 auto 庫 郵件佇列 twork_skd_mailqueue附件路徑欄 + 重建 TWORK_SKD_MAILQUEUE_DEQUEUE(讓寄送端撈得到附件)
sql/mailqueue-enqueue-deploy.sql 各 auto 庫 + log 庫 proc_param 入列 SP TWORK_SKD_MAILQUEUE_ENQUEUE(產檔後把信+附件放進佇列)
sql/alertlog-skd-deploy.sql 各 auto 庫 + log 庫 proc_param 排程資料存取改走 SKD 派發器(連帶關閉上節「金流預測跌破警戒」警示在排程環境無法入列的缺口)
sql/autopilotmethod-register-deploy.sql(選用) 各公司庫(csa) ScheduledDeck 方法字典列(僅為維護畫面 discoverability,冪等)

正式機 proc_param 為 IDENTITY

與其他排程腳本相同:正式機 proc_param 為 IDENTITY,請用腳本檔頭註明的不帶 pkid 版本;QUOTED_IDENTIFIER 須為 ON。多客戶部署時每個客戶的 auto/log/csa 庫都要各跑一次。

headless 登入憑證 / runtime 驗收

子行程 headless 產檔需登入憑證(worker appsettingsScheduledDeck 節,見headless 登入帳號;缺則任務明確失敗,嚴禁寫入版控,正式建議走 Key Vault)。端到端真寄已於 dev 驗過;行事曆 UI 選用與字典顯示的 runtime 觀察,正式啟用前建議再走一輪。

失敗處理 / 重設 'F' 狀態

'F' 狀態怎麼來

MailGun 寄信失敗、AutoPilot 動作 throw exception、或回傳 IsSuccess = false 時,排程作業會把該事件的 已通知已完成 標為 'F',並在 log 留下原因。

'F' 狀態不會被自動重撈(SP 過濾 NOT IN ('Y', 'F')),需要管理員手動處理。

重設讓事件再跑一次

在 SQL Management Studio 執行:

-- 把單一事件重新放回待處理
UPDATE [行事曆事件 base table]
   SET [已通知] = '',          -- 或 'N',依貴司 default
       [執行開始時間] = NULL
 WHERE [pkid] = '<目標 pkid>';

-- AutoPilot 同理
UPDATE [行事曆事件 base table]
   SET [已完成] = '',
       [執行開始時間] = NULL
 WHERE [pkid] = '<目標 pkid>';

批次重設今天所有失敗事件

UPDATE [行事曆事件 base table]
   SET [已通知] = '',
       [執行開始時間] = NULL
 WHERE [已通知] = 'F'
   AND CAST([預定時間] AS date) = CAST(GETDATE() AS date);

先確認失敗原因

重設前請先看 log(C:\temps\scheduler\yyyy-MM-dd.txt)確認失敗原因。如果是 Mailgun 帳號爆量、收件人 email 格式錯誤,重設只會再失敗一次。先修根因再重設。

殭屍 'X' 狀態

什麼是殭屍

排程作業在處理一筆事件時,會先把狀態標為 'X'(執行中)並寫入 執行開始時間 = DateTime.Now。正常情況下幾秒內就會更新成 'Y'(成功)或 'F'(失敗)。

但如果 process 在這當中被強制終止(OOM、Windows 關機、被 timeout 殺掉、藍屏),事件會卡在 'X' 永遠不會被更新 → 殭屍

自動回收

SP 的查詢條件包含:

[已完成] <> 'X' OR [執行開始時間] < DATEADD(MINUTE, -10, GETDATE())

意思是:'X' 狀態超過 10 分鐘的事件會被當成殭屍重新撈出。下次觸發排程作業時,這些殭屍會被重跑。

手動處理

若要立刻把殭屍復活(不等 10 分鐘):

UPDATE [行事曆事件 base table]
   SET [已通知] = '',
       [執行開始時間] = NULL
 WHERE [已通知] = 'X';

為什麼是 10 分鐘

排程是 5 分鐘觸發 + 4 分鐘 timeout,正常一次跑滿不會超過 4 分鐘。10 分鐘給予 1 個完整週期的緩衝,避免「上一輪還沒跑完」就被當殭屍。

疑難排解 (FAQ)

信沒寄出去怎麼辦?

依序檢查:

  1. 手動跑一次TsERP.SchedulerWorker.exe),看 console 輸出。如果出現 Notify pkid=... -> F (...),看括號內錯誤訊息
  2. 看 logC:\temps\scheduler\yyyy-MM-dd.txt,找最近的 [ERROR] [SchedulerJob] Notify pkid=... -> F (...)
  3. 確認 Mailgun:API Key 是否過期、Domain 是否被停用、額度是否爆
  4. 確認收件人:行事曆事件的 通知帳號 欄位是否有效 email
  5. 確認 SP:手動 EXEC dbo.TWORK_SKD_GET_PENDING_NOTIFY @datetime = GETDATE() 看有沒有撈到 row。若沒有,問題在 SP 的過濾條件
AutoPilot 沒跑怎麼辦?
  1. 確認該事件的 通知方式 不是 Email(AutoPilot 走的是 LP_P1='2' 流程,跟通知信不同)
  2. 看 log 是否有 AutoPilot pkid=... -> F
  3. 確認 IAutoPilotHelper.ExecuteMethod 對該事件回傳的 Message
  4. AutoPilot 動作本身可能依賴主程式的某些 plugin / DLL,請確認 TsERP.SchedulerWorker 部署目錄含有對應 DLL
log 看哪裡?
  • SchedulerLog(主要 log)C:\temps\scheduler\yyyy-MM-dd.txt,每天一個檔,含完整 INFO + ERROR 歷程
  • stdout / stderr:手動執行 .exe 時可在 console 看到;工作排程器啟動時 stdout 會被丟棄(已不需要靠 stdout 看歷程,SchedulerLog 已涵蓋)
  • Mailgun 端Mailgun Logs
  • 行格式yyyy-MM-dd HH:mm:ss [LEVEL] 訊息,例:2026-05-05 02:30:00 [INFO] [SchedulerJob] === DB darbnogilog1 on localhost\t2022bin ===
exit code 1 跟 2 有什麼差別?
exit code 含義 處理方式
0 全部成功(含「沒有資料要處理」) 無需處理
1 部分失敗:某個 DB 的 NotifyAsync 或 AutoPilotAsync 拋例外,但其他 DB 仍跑完 看 log 找哪個 DB 失敗,多半是 SP 沒改、權限不足、SQL 連線問題
2 致命錯誤:根本沒進入主流程(DI 註冊失敗、appsettings 解析失敗、Mutex 競爭以外的問題) 看 stderr 第一行 Fatal error: ...,通常是設定檔錯誤或缺 DLL
排程作業會跟正在開 ERP 的使用者衝突嗎?

不會。新版 ERP 主程式的 MainWindowBtnTimerViewModel移除 TimerBll 欄位,使用者端不會再啟動本機 timer。所有通知/AutoPilot 都由 server 上的排程作業統一處理,避免重複。

舊版 client 升級前的過渡期

若 server 已啟用排程作業、但部分使用者還在用舊版 ERP client(仍含 TimerBll 啟動邏輯),會出現重複通知。請務必同步升級所有 client,或暫時停用 server 排程作業。

前一次還沒跑完,下一次又被觸發了,怎麼辦?

程式內以 Global\TsERP_Scheduler Mutex 保護單一執行;後到的 process 會印 Another scheduler instance is running. Exiting. 後 exit code 0 結束。工作排程器層級也設了「不要啟動新的執行個體」雙保險。

若想驗證,可手動連續執行兩次 TsERP.SchedulerWorker.exe,第二次應立即結束。

排程作業要怎麼停?

工作排程器 → 找到 TsERP Scheduler停用。已在執行中的 process 會跑完當下這輪,不會被中斷(除非超過 hard timeout)。

若要立即終止 process:在工作管理員 kill TsERP.SchedulerWorker.exe。下次觸發前殭屍 'X' 狀態會自動回收。

可以調整觸發間隔嗎?

可以。修改工作排程器觸發程序的「重複工作每 5 分鐘」為其他值即可。但請注意:

  • 間隔太短(< 2 分鐘)會頻繁敲 SQL,影響效能
  • 間隔太長(> 30 分鐘)通知信延遲體感很差
  • 改間隔後請同步調整 hard timeout(建議 = 觸發間隔 - 1 分鐘)與 SP 的殭屍判定門檻(建議 = 觸發間隔 × 2)

截圖補充清單

以下截圖建議補上,可放於 docs/admin/images/

  • [需補:scheduler-task-general.png] — 工作排程器「一般」分頁完成設定畫面
  • [需補:scheduler-task-trigger.png] — 工作排程器「觸發程序」分頁,5 分鐘重複設定畫面
  • [需補:scheduler-task-action.png] — 工作排程器「動作」分頁,指向 exe 的設定畫面
  • [需補:scheduler-task-settings.png] — 工作排程器「設定」分頁,4 分鐘 timeout 與「不要啟動新的執行個體」畫面
  • [需補:scheduler-console-output.png] — 手動執行 exe 的 console 成功輸出畫面
  • [需補:scheduler-log-folder.png]C:\temps\scheduler\ 內的 log 範例
  • [需補:scheduled-deck-event.png] — 行事曆事件掛「排程報表自動寄送」:單據別=CUSTOMDASH執行方法=ScheduledDeck、參數 JSON 與人話「參數顯示」
  • [需補:scheduled-deck-appsettings.png] — worker 設定檔 ScheduledDeck 節(headless 使用者代號/密碼;示範用測試帳號)
  • [需補:scheduled-deck-mail.png] — 收件匣收到的排程報表信(主旨「【排程報表】…」與 Excel/HTML 附件)
  • [需補:scheduled-deck-event-mobile.png] — 行事曆事件參數:格式HTMLMOBILE發布網址=Y 的設定畫面
  • [需補:scheduled-deck-mobile-view.png] — 手機瀏覽器開啟行動版附件的畫面(直立卡片流、KPI 置頂、零值隱藏效果)
  • [需補:scheduled-deck-mail-mobile-attach.png] — 收件匣信件內文把行動版附件標為「(行動版)」的畫面

相關功能

  • L 聯絡傳訊:行事曆通知對應的 Email 寄送設定
  • A 系統管理:使用者帳號與權限(排程作業使用 SchedulerUser 假身份)