修改紀錄
每次新增功能、變更、刪除、Breaking Changes 的時間軸。共 29 篇。
📅 2026 年 5 月 (19 篇)
-
05-21 tserp-info-site ▸
2026-05-21 — 對外資訊網 manual.yanyue.io
摘要
建立對外公開資訊網
manual.yanyue.io(Cloudflare Pages),三頁 + 既有 mkdocs 手冊整合:- 系統地圖:259 個程式扁平單一清單,可搜尋 / 過濾 / 排序
- 修改紀錄:28 篇按月分組
- SQL 更新:4 個 migration(從 changelog
## Migration Notes抽)
- 使用手冊:mkdocs Material build,每頁頂部自動注入「程式資訊」框
詳細決策見 ADR 0020。
新增
docs/
docs/system-map/_subsystems.yml— 18 子系統對照(key + name + 排序 + 短描述 + TsERP / ViewModel / ProgramClass 三端路徑映射)
docs/system-map/README.md— 編輯指南(兩分鐘新增一個程式)
- **
docs/<sub>/*.md× 259** — 系統地圖骨架,bootstrap 自動產出
- 含 YAML frontmatter(id / name / status / features / xaml)
- 含自動產生 body(用途 / 主要欄位 / 操作步驟草稿 / 待確認,全標 ⚠️)
- 14 個 dead-code 候選跳過(見
docs/_scan/follow-ups.md)- 4 個變體標
variant_of:收編tools/release-notes/
工具與腳本:
檔 用途 build.ps1主 build 入口 publish.ps1build + wrangler pages deploybootstrap-system-map.ps1從 snapshot 一次性產 .md 骨架 Fill-Features.ps1從 XAML + VM 抽 features lib/Parse-Frontmatter.ps1YAML + body 解析 lib/Parse-SystemMap.ps1掃 docs/<sub>/*.md lib/Parse-Changelog.ps1掃 changelog lib/Parse-Sql.ps1從 changelog 抽 SQL block lib/Validate-SystemMap.ps1frontmatter 驗證(含 Levenshtein 拼錯建議) lib/Render-Html.ps1markdown → HTML、template token 替換 lib/Preprocess-Docs.ps1注入 metadata admonition 給 mkdocs templates/*.html× 5_layout / index / system-map / changelog / sql public/style.css純 CSS(無 framework) public/search.jsvanilla JS:搜尋 / 排序 / chip filter / URL state / 列展開 變更
docs/_template/feature-template.md— 在最上方補 frontmatter 段(id / name / status / features / xaml 等選填欄位)
.gitignore— 加入docs/_scan/、tools/release-notes/dist/、tools/release-notes/docs-built/、mkdocs-built.yml
刪除
無
Breaking Changes
無 — 純新增,不影響既有 ERP 程式碼、不改 DB、不動 mkdocs nav。
Migration Notes
部署到 manual.yanyue.io(首次)
# 1. 設環境變數 [Environment]::SetEnvironmentVariable('CLOUDFLARE_API_TOKEN', '<token>', 'User') [Environment]::SetEnvironmentVariable('CLOUDFLARE_ACCOUNT_ID', '<account-id>', 'User') # 關 PowerShell 重開 # 2. Build + Deploy cd C:\ERPV2\.claude\worktrees\hungry-brahmagupta-825ce9 pwsh tools\release-notes\publish.ps1publish.ps1 會:
1. 跑 build(驗證 → 解析 → 渲染 → mkdocs build)
2.
wrangler pages project create tserp-info(若不存在)3.
wrangler pages deploy ./dist --project-name=tserp-info首次部署後到 Cloudflare Dashboard → Pages → tserp-info → Custom domains 加
manual.yanyue.io。後續更新
只要編輯
docs/<sub>/*.md、docs/changelog/*.md或docs/system-map/_subsystems.yml,然後重跑publish.ps1即可。環境需求
- PowerShell 7+(pwsh)
- powershell-yaml:
Install-Module powershell-yaml -Scope CurrentUser
- Node.js 20+(wrangler 3 支援)
- mkdocs material:
pip install -r docs/requirements.txt
- Cloudflare API Token(權限 Account → Cloudflare Pages → Edit)
對使用者的影響
- 客戶可在
manual.yanyue.io看到系統有哪些功能、最新變動、要跑哪些 SQL
- 我們維護方式不變(編
docs/<sub>/*.md或docs/changelog/*.md),多了 frontmatter 那段
- depth-C 自動草稿需人工複核(用途 / 操作步驟 / 主要欄位 / 待確認,全部標 ⚠️)
連結
- 掃描原始:
docs/_scan/INDEX.md(gitignored,留本機參考)
- follow-up bug 清單:
docs/_scan/follow-ups.md
- 編輯指南:
docs/system-map/README.md
-
05-18 scx_ord-db-model-sync ▸
2026-05-18 — SCX_ORD DB vs Model/Source 同步
摘要
跑
/db-model-compare SCX_ORD比對資料庫 view/table 欄位與Common/Model/E.Order+Model/Source/E.Order內各 Model/Source 類別,做了三層修補:1. 補 92 個 DB 欄位到 Model/Source(之前 Source 沒包到的中文欄位)
2. 補 64 處
[NotMapped]/[Browsable(false)]標記(Source 端的 UI helper props 沒打標記)3. 砍掉英文 stale 屬性 + caller 改名:原本英文 DB 改成中文 DB 後,部分 Source 還留著綁到不存在英文欄位的舊屬性,這次清掉並把 caller(C# + XAML)改用新中文名
變更
1) Model 補欄位(90 個屬性、18 個檔組)
每個檔組(Model + Source pair)都把 DB view/table 內的中文欄位補進
Common/Model/E.Order/<Name>Model.cs與Model/Source/E.Order/<Name>Source.cs:- Twork_ord_vw_ormast(24 model + 26 source)
- Twork_ord_vw_ordetl(31 + 31)
- Twork_ord_vw_cuqtm(3 + 3)
- Twork_ord_vw_cuinve(5 + 5)
- Twork_ord_vw_cupdt(6 + 6)
- Twork_ord_vw_ci_m(5 + 5)
- Twork_ord_vw_preord_d(4 + 4)
- Twork_ord_vw_cupdma(2 + 2)
- Twork_ord_vw_salema(3 + 3)
- Twork_ord_vw_salede(3 + 3)
- 其他 8 個小檔(cuqtd、pkde、cuinvema、pkma、cudata、cudataspec、preord_m、cuqtd1)共 4 + 4
2) Source 補 NotMapped/Browsable(false) 標記(64 處)
凡是 Source 屬性沒對到 DB 欄位、又沒打
[NotMapped]或[Browsable(false)]標記的(多半是 UI binding helper),都補上:- Twork_ord_vw_ormast(18)
- Twork_ord_vw_ordetl(11)
- Twork_ord_vw_cuqtm(8)
- 其他 17 個檔共 27 處
3) 砍英文 stale 屬性 + caller 改名
Source 端砍掉
Source 砍掉 取代為(已存在) Twork_ord_vw_cuinve QuantityPerCarton包裝量_箱Twork_ord_vw_cuqtd VendorName+_VendorNamefield廠商簡稱Twork_ord_vw_cuqtm VendorName+CustomerName+ 2 backing fields廠商簡稱/客戶簡稱Twork_ord_vw_ordetl ETA,ShippedQuantity預訂交期/已交數量Twork_ord_vw_preord_m CustomerName+_CustomerNamefield客戶簡稱RmamModel + RmamSource SysEntryDate / SysEntryUser / SysEntryMachine / SysRowStatus / SysIsChosen / SysControlCode(6 個英文版重複 ModelBase 中文版的審核欄位)ModelBase 內建 輸入日期 / 輸入人員 / 輸入地點 / 增刪修 / 選擇 / 管制碼判斷依據:
vw_cuinve.QuantityPerCarton、vw_ordetl.ETA / ShippedQuantity由 modeljson 確認舊→新(DB 已有對應中文欄)
CustomerName / VendorName同 Source 內已存在我這批新加入的客戶簡稱 / 廠商簡稱,舊版只是 UI helper、DB 沒有對應英文欄位
- Rmam 系列 6 個審核欄位重複 ModelBase 的 Chinese 版本,砍掉後乾淨繼承
C# caller 改名(10 處 LogicBll + 4 處 ViewModel)
LogicBll/E.Order/Cuqt/CuqtBll.cs:VendorName = ...→廠商簡稱 = ...
LogicBll/E.Order/Odtosl/OdtoslBll.cs、OdtosladdiBll.cs:nameof(ETA)/nameof(ShippedQuantity)→ 中文
LogicBll/Ade/E.Order/OrmastSave.cs、OrmastDelete.cs:d.ShippedQuantity/d.ETA→ 中文
LogicBll/E.Order/Darbedittool/DarbedittoolBll.cs:d.ETA = ...→d.預訂交期 = ...
ViewModel/D.Bom/Invent/Condition/SalesOrderConditionViewModel.cs:query condition string 內nameof(ShippedQuantity)→nameof(已交數量)
ViewModel/E.Order/OrmastStockQueryViewModel.cs:同上
ViewModel/E.Order/OrmastViewModel.cs:o.ETA = ...→o.預訂交期 = ...
ViewModel/E.Order/Ormast_sViewModel.cs:LINQ projection 內a.ETA/a.ShippedQuantity→ 中文
XAML caller 改名(5 檔、13 處)
XAML binding 改名 TsERP/E.Order/Cuinve.xaml {Binding QuantityPerCarton}→{Binding 包裝量_箱}TsERP/E.Order/Cuqt.xaml CustomerName× 2 /VendorName× 3 → 中文(master + grid column)TsERP/E.Order/Ormast.xaml ETA/ShippedQuantitygrid column → 中文TsERP/E.Order/Ormast_US.xaml 同 Ormast 3 處 TsERP/E.Order/Preord.xaml CustomerName× 2 →客戶簡稱工具鏈調整
- 4 個檔(vw_ormast、vw_pkde、vw_cupdt)原本 agent 寫
[TsDecimalPrecision(...)],但專案內只有TsDecimalLengthAttribute,統一改[TsDecimalLength(...)]
Breaking Changes
對「外部 caller / plugin」:
Twork_ord_vw_ordetlSource上沒了ETA、ShippedQuantity屬性,要用預訂交期/已交數量
Twork_ord_vw_cuqtmSource/Twork_ord_vw_cuqtdSource上沒了VendorName、CustomerName,要用廠商簡稱/客戶簡稱
Twork_ord_vw_cuinveSource上沒了QuantityPerCarton,要用包裝量_箱
Twork_ord_vw_preord_mSource上沒了CustomerName,要用客戶簡稱
RmamModel/RmamSource上沒了SysEntryDate / SysEntryUser / SysEntryMachine / SysRowStatus / SysIsChosen / SysControlCode,要透過 ModelBase 繼承的輸入日期/輸入人員等中文版
對使用者的影響
- 原本英文屬性綁的畫面:本次同步後改用中文版,runtime 行為應該等價(畢竟英文版本來就沒接到實際 DB 欄位、值是空的)
- vw_ormast 修活:原本 Source 沒包到
訂單編號 / 訂單別 / 客戶簡稱等核心欄位,畫面上對應欄位都是空的(XAML binding 找不到屬性靜默失敗);本次補上後 grid 該顯示正常
待人工複查 / 還沒清的部分
仍有 ~140 個英文 stale 屬性(多半在 ormast,SHEMAIL / CARDTYPE / AUDIT0X 等 EDI / legacy 欄位)沒清,分類如下:
- NoChange (69):modeljson 說欄位沒重命名,但實際 DB 查不到 → modeljson 過時或欄位廢除
- Orphan (73):modeljson 完全沒記載 → 多半是 ormast 內 EDI / legacy 英文欄位
- Renamed but no DB match (1):vw_ordetl 的
ETD(modeljson 說預計出貨日但 DB 沒此欄)
- SemanticGuess (1):vw_cuqtm 的
GroupDescription同檔沒中文版可配對
這些要等下一輪掃過再決定砍 / 補 / 保留為純 UI 屬性。
Build 狀態
Common.csproj/Model.csproj/ViewModel.csproj/LogicBll.csproj/TsERP.csproj全部 0 CS / 0 MC 錯誤、0 BG1002
連結
- 相關 skill:
.claude/skills/db-model-compare
- modeljson 權威來源:
D:\visualstudio\Generator\類別產生器\類別產生器\bin\Debug\net8.0-windows\modeljson\
-
05-17 tspage-to-tsview-batch ▸
2026-05-17 — TsPage → TsView 大批次遷移 + 拆 SqlEdit wrapper + RadGridView header 寫死中文
摘要
接續本日的 obs-to-datamanager(
fccf4d73)與 dg-to-radgrid(b9473cff)兩個批次,把同一批 52 個畫面遷移到TsView:1. 階段 A:42 個 XAML 從
TsPage改成TsView(含x:Name、ElementName、AncestorType、code-behind base class)2. 階段 B:同 42 個 XAML 移除
SqlEdit/SqlEditInventory外層 wrapper3. 階段 C′:C.GeneralAffair 4 個 XAML 把
RadGridView的Header={Binding DataContext.DisplayMeta[X]}寫死成Header="X",共 29 處4. 順手把 Ppurch.xaml(已 TsView)殘留的 2 處
ElementName=Page修掉剩下 10 個檔(C.GeneralAffair 6 + G.Purchasing Ppurch/Purch/Sppdt + R.HumanResource/Persmems)昨天遷移階段已轉好 TsView,今天不動。
變更
階段 A(42 個 XAML + 42 個 .xaml.cs)
XAML 內每檔做:
<TsControl:TsPage>/</TsControl:TsPage>→TsControl:TsView(含.TsPage.Resources→.TsView.Resources)
x:Name="Page"→x:Name="View"
ElementName=Page→ElementName=View
AncestorType=Page→AncestorType=TsControl:TsView
.xaml.cs內:: TsPage→: TsView。階段 B(42 個 XAML)
刪掉外層 wrapper(單行或多行):
<TsControl:SqlEdit ...>↔</TsControl:SqlEdit>
<TsControl:SqlEditInventory ...>↔</TsControl:SqlEditInventory>(H.Inventory 的 Trlima/Trlost/Trrama/Trsama)
內嵌使用的
TsControl:SqlEditTurn不動。階段 C′(4 個 XAML,29 處 header)
Header="{Binding DataContext.DisplayMeta[X], ElementName=View, FallbackValue=X}"→Header="X":- Ginvent (2 處)
- Gppurch (12 處)
- Gsplace (3 處)
- Gsppdt (12 處)
只動
tsTelerik:TsRadGridView內的 columnHeader;TsLabel/TsTextBlock的Content維持走 DisplayMeta 多語系。Breaking Changes
對「外部 caller / plugin」:
- View 類型從
TsPage(System.Windows.Controls.Page)變成TsView(UserControl)。原本靠 WPF Page navigation 開啟畫面的程式碼會壞,要改成 ContentControl swap(這套專案的 navigation 用 VM + DataTemplate 切換,主流程影響很小)
- code-behind 任何
(TsPage)this.xxx強制轉型會壞,要改TsView
- 外層 SqlEdit 沒了,下游的
BindingData推 DataContext 行為消失;如果有 binding 假設BindingData推下來的 DataContext,要手動補DataContext="{Binding ...}"
對使用者的影響:丟失的屬性
11 個檔失去 `HaveWorkflow="True"`
檔 原本行為 Purchasing/G.Purchasing/VendorDefect.xaml workflow 按鈕 / ADE 編輯狀態切換 Purchasing/G.Purchasing/VendorFillRate.xaml 同上 TsERP/E.Order/Ci.xaml 同上 TsERP/E.Order/Cupdma.xaml 同上 TsERP/E.Order/Pk.xaml 同上 TsERP/E.Order/Preord.xaml 同上 TsERP/F.Production/OutSourcingFillRate.xaml 同上 TsERP/G.Purchasing/VendorDefect.xaml 同上 TsERP/G.Purchasing/VendorEvaluation.xaml 同上 TsERP/G.Purchasing/VendorFillRate.xaml 同上 TsERP/H.Inventory/Trinout.xaml 同上 VM 若沒自行處理,這些頁面會失去 workflow 功能。需要後續補回(可考慮在 VM constructor 設旗標,或在 View 內加上其他 workflow 控制元件)。
其他失去的屬性
- TsERP/D.Bom/Invent_prime.xaml:
FirstRowSpace="25"+PkidVisibility="{Binding PkidVisibility}"
- TsERP/E.Order/Ormast_US.xaml:
FirstRowSpace="25"
FirstRowSpace主要影響表頭第一列的間距,視覺差異;PkidVisibility影響 Pkid 欄是否可見,邏輯影響。Column Header 寫死中文(C′)
C.GeneralAffair 4 檔的 grid header 不再走 DisplayMeta 取多語系,目前只顯示中文。
Common/Lang/Xaml/<檔名>.json內的 key 仍保留供TsLabel/TsTextBlock使用。過程中要修的問題
階段 B 的 worker agent 在 14 個檔(G.Purchasing+J.QC+K.Cost+M.EIS+P.Invoice 共 14 個)把
</TsControl:SqlEdit>替換成</Grid>而不是直接刪除,多出 1 個</Grid>close tag。Build 拋 MC3000 抓到,已用 PowerShell regex 統一拔掉檔尾多餘的</Grid>。Build 狀態
TsERP.csproj:連續 2 次 clean build 都是 0 XAML/CS 錯誤、0 BG1002 cascading
- 沒有看到 MC1000 mscorlib 工具鏈 hiccup(前兩個批次的常見 noise)
待人工複查
1. 11 個失去 HaveWorkflow 的檔:如果原本有用 workflow,要決定怎麼補回(VM 內處理 / View 內加控制 / 跳過)
2. Invent_prime 的
PkidVisibility:Pkid 欄顯隱規則重要,建議加回 VM 控制3. Ormast_US.xaml 的 SqlEdit 內直接子元素是
TsControl:TsTabControl不是Grid。TsView 只能一個直接子元素 — 目前編譯過了,runtime 行為要實測4. 縮排:刪 wrapper 後內層內容多了一層縮排,沒整理。要的話另跑 formatter
連結
- 本日批次:
- obs-to-datamanager:
fccf4d73- dg-to-radgrid + 5 個新 SeekGridViewColumn:
b9473cff- tspage-to-tsview(本檔)
- 範本:
TsERP/N.GeneralLedger/Accl.xaml(Master only)、TsERP/N.GeneralLedger/Sliy.xaml(Master + Detail)
- 相關 skill:
.claude/skills/tspage-to-tsview
-
05-17 obs-to-datamanager-batch ▸
2026-05-17 — ObservableCollection → DataManager 大批次轉換
摘要
把剩餘 67 個 ViewModel 的
ObservableCollection<T> Master/Detail/DetailN全面轉成DataManager<T> MasterData/DetailData/DetailNData,並同步更新對應 XAML 與外部 caller。此前已有 ~30 個 VM 在歷次零散轉換中完成,本次把剩下的全部清掉,達到專案內主資料 binding source 一致統一。順帶修活了多支「XAML 已寫成
{Binding MasterItem}但 VM 沒呼叫 Register、實際畫面空白」的程式(典型範例:Jobcode)。執行流程:分 B1(pilot 1 個)→ B2(單 Master 12 個)→ B3(Master+Detail 30 個)→ B4(3-5 sources 14 個)→ B5(部分轉換 cleanup 10 個含 Splace)。每批跑
dotnet build驗證。變更
ViewModel 層(67 檔,按模組分布)
模組 檔數 代表檔 C.GeneralAffair 9 Gtrlima, Gtrinout, Gsppdt, Gppurch, Gsplace, Gpurch, Ginvent, Gtrrama, Persmems(R) E.Order 14 Ci, Cudatam, Cuinve, Cupdma, Fillrate, Pk, Preord, Rma, Salema, Sample, Ormast, Cuqt, WarrantyCard, Shiptoaddress, Visit P.Invoice 10 Tother, Ticks, Ticksr, Tickr, Tickmin, Ticka_voidrp, Ticka_rej, Ticka_disc, Tick3, Ticka H.Inventory 11 Jobcode, Invrec, Bol, Splace, Trlost, Trinout, Trlima, Trrama, Trsama, MasterFile, (含 Splace cleanup) G.Purchasing 7 Purch, Ppurch, TrrafeePrime, Sppdt, VendorDefect, VendorFillRate, VendorEvaluation J.QC 5 Wkrama, Twrama, Repair, Purama, Complain F.Production 4 Wkpaper, Wkday, Pdtinf, OutSourcingFillRate Q.FixedAsset 2 Asset, Accexp D.Bom, K.Cost, M.EIS, Z.System 各 1 Invent_prime, Cost, ProductionPlan, Contract 每個 VM 的處理:
public ObservableCollection<T> XxxName { get/set + _backing }→public DataManager<T> XxxNameData { get; private set; }
- 構造子加
XxxNameData = ItemsBindingManager.Register<T>();(順序對應原本GetBindingSourcesPropertyName的 obj[n] 順序)
- 若 binding sources 全轉了,整段刪除
GetBindingSourcesPropertyName()override
- usages 替換:
.Count > 0→.FirstItem != null、[0]→.FirstItem、[idx]→.ObservableCollection[idx]、foreach→.ObservableCollection、.Add→.AddItem、.Clear→.Clear、= new ObservableCollection<T>(query)視語意改SeedFromRemote或AddRange
masterListenerHelper.SetProperty(ref _Master, value)→ 構造子裡MasterData.AddListener(helper)
XAML 層(~60 檔)
{Binding MasterItem}→{Binding MasterData.GridView.CurrentItem}
{Binding Master[0]}→{Binding MasterData.GridView.CurrentItem}
ItemsSource="{Binding Master}"→ItemsSource="{Binding MasterData.GridView}"
ItemsSource="{Binding Detail}"→ItemsSource="{Binding DetailData.GridView}"
ItemsSource="{Binding DetailN}"→ItemsSource="{Binding DetailNData.GridView}"
{Binding Master.Prop}→{Binding MasterData.GridView.CurrentItem.Prop}
含
Purchasing\G.Purchasing\VendorFillRate.xaml與Purchasing\G.Purchasing\VendorDefect.xaml(重複位置)一併修。外部 caller 與 BLL 簽章(17 處)
修了非主目標但會 reference
viewModel.Master/.Detail的下游:ViewModel/E.Order/WarrantyContentSpecialQueryViewModel.cs
ViewModel/E.Order/Ci_m1ViewModel.cs
ViewModel/E.Order/PreordControlViewModel.cs
ViewModel/E.Order/OrmastStockQueryViewModel.cs
ViewModel/E.Order/Ormast_sViewModel.cs
ViewModel/H.Inventory/InvrecmViewModel.cs
ViewModel/H.Inventory/WhseChangeViewModel.cs
ViewModel/H.Inventory/ItemTransferViewModel.cs
ViewModel/H.Inventory/PickingAdjustViewModel.cs
ViewModel/H.Inventory/StkMoveViewModel.cs
ViewModel/H.Inventory/BOLImportViewModel.cs
ViewModel/H.Inventory/TrramaAllocationViewModel.cs
ViewModel/H.Inventory/TrramaAllocationQueryViewModel.cs
ViewModel/H.Inventory/TrramaAPQueryViewModel.cs
ViewModel/H.Inventory/TrsamaPickingQueryViewModel.cs
ViewModel/C.GeneralAffair/GtrramaAllocationQueryViewModel.cs
LogicBll/E.Order/Cuqt/CuqtBll.cs:Import/AddDetail/AddDetail1參數從ObservableCollection<T>改DataManager<T>、.Add→.AddItem
Breaking Changes
對「外部 caller」(非本批內的 production code,例如 plugin / 自訂 BLL):
- VM 的
Master/Detail/DetailN屬性消失,改用MasterData/DetailData/DetailNData,且改成{ get; private set; }(不能再從外部賦值)
- 若外部要對清單操作:
.Add(item)→.AddItem(item)、.Clear()→.Clear()、.Count→.ObservableCollection.Count、[0]→.FirstItem
- 若外部要替換整批資料:原本
vm.Master = new ObservableCollection<T>(data)不能用,改vm.MasterData.SeedFromRemote(data.ToList())(不標 dirty)或vm.MasterData.AddRange(data)(會標 IsNew/IsDirty)
CuqtBll公開方法簽章變更:呼叫端要傳DataManager<T>而非ObservableCollection<T>
對使用者的影響
- 終端使用者大部分操作無感
- 修活畫面:原本 XAML 寫
{Binding MasterItem}但 VM 沒 Register 的程式(典型如 H.Inventory\Jobcode),本來開啟畫面什麼都沒有,現在正常顯示與編輯
- 可能行為改變:6 處
Detail.Insert(index, item)被改成DetailData.AddItem(item)(append 到尾端),如果原本有業務邏輯依賴「插入到特定位置」,使用者觀察到的順序會不同。詳見「待人工複查」
待人工複查
1. Insert(index) → AddItem 行為改變(6 處,已在原位用 `// NOTE:` 標註)
DataManager<T>目前沒有Insert(int index, T item)API,全改成 append。若這些位置有業務語意,要考慮在DataManager<T>補InsertItem(int index, T item):ViewModel/E.Order/Ci_m1ViewModel.cs:~166
ViewModel/E.Order/PreordControlViewModel.cs:~282
ViewModel/G.Purchasing/PurchViewModel.cs:~219
ViewModel/H.Inventory/TrlimaViewModel.cs:~340(2 處)
ViewModel/H.Inventory/WhseChangeViewModel.cs:~242
ViewModel/H.Inventory/PickingAdjustViewModel.cs:~146
2. 測試檔已壞,未修
下列測試檔用了
viewModel.Master = new ObservableCollection<>()物件初始化器語法,現在 setter 拿掉了會編譯失敗:UnitTest1/UploadTest/PurchasingTests.cs
UnitTest1/UploadTest/InventoryTests.cs:46, 134
Test_SqlModify/UploadTest/PurchasingTests.cs
Test_SqlModify/UploadTest/InventoryTests.cs:46, 134
Migration:物件初始化器中的
Master = new ObservableCollection<T> { ... }改成在arrange後另寫一句viewModel.MasterData.AddRange(new[] { ... })。Detail 同理。3. MasterFileViewModel 無對應獨立 XAML
ViewModel/H.Inventory/Edi_Export/MasterFileViewModel.cs是 nested VM、沒有獨立 .xaml 檔。VM 已轉,但實際上Edi_Export.xaml內並未綁定到它(tab DataContext 是Edi856ViewModel/Edi810ViewModel)。需要再確認這個 VM 是否還有用,若無可考慮整檔刪除。Build 狀態
ViewModel.csproj:exit 0、0 errors
TsERP.csproj:exit 0、0 errors
UnitTest1.csproj/Test_SqlModify.csproj:4 個檔需修才會通過(見「待人工複查 2」)
還剩什麼 ObservableCollection?
完成後仍有
ObservableCollection使用的檔案大多屬於:- ComboBox / dropdown 來源(非 binding source,不需轉)
- 統計查詢、暫存 list、UI 內部 buffer
- Helper / infrastructure(
ObservableCollectionExtensions、HistoryViewModel等)
不在這次轉換目標內。
-
05-17 dg-to-radgrid-batch ▸
2026-05-17 — TsDataGrid → TsRadGridView 大批次轉換 + 補建 SeekGridViewColumn
摘要
接續上午的
ObservableCollection → DataManager遷移(commitfccf4d73),把同一批 52 個 XAML 的TsDataGrid全部換成TsRadGridView,並補上對應DataManager="{Binding XxxData}"、把所有欄位類型/屬性照新版 API 改掉。過程中發現 6 個常用 Seek 欄位(Customer / Vendor / SalesRep / Bin / SKUGroup / Processing)在 Telerik 版下沒有對應類別,補建 5 個新類別(Processing 沿用既有)。變更
新增類別(5 個,`TsControl/TsTelerik/TsRadGridViewColumn/`)
類別 預設 Header / SeekCondition / SeekColumn ExistTable TsCustomerSeekGridViewColumn客戶代號 / 客戶代號 / 客戶簡稱 Cudata TsVendorSeekGridViewColumn廠商代號 / 廠商代號 / 廠商簡稱 Spdata TsSalesRepSeekGridViewColumn業務員編號 / 業務員編號 / 姓名 salesmen TsBinSeekGridViewColumn倉儲代號 / 倉儲代號 / 倉儲說明 Splace TsSKUGroupSeekGridViewColumn群組代號 / 群組代號 / 說明(DataMemberBinding=產品群組) Grpm 全部繼承
TsSeekGridViewColumn,行為與 V2 / DataGridV2 版本對等。預設綁中文欄位;XAML 若有需要可用DataMemberBinding覆寫。XAML(52 + 8 + 6 = 60 處/檔次)
容器與欄位
<TsControl:TsDataGrid>/<local:TsDataGrid>→<tsTelerik:TsRadGridView>
<DataGrid.Columns>/<TsControl:TsDataGrid.Columns>→<tsTelerik:TsRadGridView.Columns>
- 加上
xmlns:tsTelerik、xmlns:tsRadGridViewColumn
- Style key
ERPDataGrid→ErpDataGridView
- 每個
ItemsSource="{Binding XxxData.GridView}"補DataManager="{Binding XxxData}"
欄位類型對照
舊 新 TsDataGridTextColumnTsGridViewDataColumnTsDataGridNumericColumnTsGridViewDataNumericColumnTsDataGridTemplateColumnTsGridViewDataColumn(含 CellTemplate)TsDataGridComboBoxColumnTsGridViewComboBoxColumn(搭 BindingProxy)v2:TsDateColumnV2TsGridViewDataDateColumnTsSKUSearchColumn/TsSkuSeekColumnTsSkuSeekGridViewColumnTsCuspSearchColumnTsCuspSeekGridViewColumnTsBankAccountSeekColumnTsBankaccountSeekGridViewColumnTsDepartmentSearchColumnTsDepartmentSeekGridViewColumnTsGLAccountSearchColumnTsGLAccountSeekGridViewColumnTsGroupSeekColumnTsGroupSeekGridViewColumnTsIndexSearchColumnTsSortSeekGridViewColumnTsSKUFixedAssetSearchColumnTsSkuFixedAssetSeekGridViewColumnv2:TsUomColumnTsUomGridViewColumnTsVendorSearchColumn★TsVendorSeekGridViewColumnTsSalesRepSearchColumn★TsSalesRepSeekGridViewColumnTsBinSearchColumn★TsBinSeekGridViewColumnTsCustomerSeekColumn★TsCustomerSeekGridViewColumnTsSKUGroupSeekColumn★TsSKUGroupSeekGridViewColumnTsProcessingSearchColumn/TsProcessingSeekColumnTsProcessingSeekGridViewColumn★ 標記者本次新建類別。
屬性轉換
Binding="{Binding X}"→DataMemberBinding="{Binding X}"
MaskInfo="ShortDate"(在日期欄位)→ForceReadOnly="True"
BindingName="X"(V2 日期欄位)→DataMemberBinding="{Binding X}"
- Seek 欄位
Description="{Binding X, ...}"→DescriptionBindingPath="X"
- 欄位
Visibility=+VisibilityConverter→IsVisible=(容器 Visibility 維持)
- 移除:
ProgramClass、SelectFirstOnItemsChanged、ClipboardContentBinding="{x:Null}"、NeedTranslate、CheckValidate、CanUserReorder、AvoidEdit、欄位上殘留的DataGridTextColumn.ElementStyle/EditingElementStyle
TsGridViewComboBoxColumn用 BindingProxy 處理ItemsSourceBinding
- ElementStyle/EditingElementStyle 內含的 TextWrapping / AcceptsReturn 改用
CellTemplate/CellEditTemplate重做(Ormast_US、Persmems)
- Button template 改
TsButtonInCell+CellStyle="{StaticResource ButtonCell}"
涵蓋檔案(52 XAML)
C.GeneralAffair (6):Ginvent、Gppurch、Gsplace、Gsppdt、Gtrlima、Gtrrama
E.Order (13):Ci、Cudatalimit、Cuinve、Cupdma、Cuqt、Fillrate、Ormast、Ormast_US、Pk、Preord、Rma、Salema、Sample
F.Production (4):OutSourcingFillRate、Pdtinf、Wkday、Wkpaper
G.Purchasing (6+2):Ppurch、Purch、Sppdt、VendorDefect、VendorEvaluation、VendorFillRate(+ Purchasing 專案 mirror 2 個)
H.Inventory (8):Bol、Invrec、Splace、Trinout、Trlima、Trlost、Trrama、Trsama
J.QC (4):Complain、Purama、Twrama、Wkrama
P.Invoice (5):Ticka、Ticka_disc、Tickr、Ticks、Ticksr
其他:K.Cost/Cost、M.EIS/ProductionPlan、D.Bom/Invent_prime、R.HumanResource/Persmems
Breaking Changes
對「外部 caller / plugin」:
- 上述 52 個 XAML 的 grid x:Name 仍可用,但是元件類型變了。code-behind 若有
(TsDataGrid)detailDataGrid.xxx強制轉型會壞 — 全部要改成TsRadGridView。
- 舊 column 類別屬性差異會影響 templated style:
-
Binding→DataMemberBinding-
Description字串 →DescriptionBindingPath字串(只取 path,不接 Binding 標記)- column 上
Visibility不能用,改IsVisible(bool)TsDataGrid上的下列 API 在新版不存在:NeedTranslate、CheckValidate、CanUserReorder(column)、AvoidEdit、column 上ProgramClass、SelectFirstOnItemsChanged
- 新建 5 個 SeekGridViewColumn 預設 binding 用中文(客戶代號 / 廠商代號 / 業務員編號 / 倉儲代號 / 產品群組);若 row 物件屬性為英文,XAML 必須用
DataMemberBinding覆寫
對使用者的影響
- 一般操作無感
- 修活的畫面:Jobcode 之類「XAML 寫對但 VM 沒 Register」的程式(昨天 obs-to-datamanager 那批同樣狀況)正常顯示
- 行為觀察點:
-
TsProcessingSeekGridViewColumn中文預設(SeekCondition=工程代號)被 Cuqt / Wkpaper / Gsppdt 用英文 DataMemberBinding 覆寫綁;seek popup 查 PROCNOS 表的欄位是中文,要確認該表中英欄位齊全-
TsSalesRepSeekGridViewColumn中文預設被 Cuinve 用英文(SalesRepCode)覆寫,同樣風險-
TsCustomerSeekGridViewColumn在 Cudatalimit 沒 DataMemberBinding 覆寫 → 會用類別預設客戶代號,需要該 row 物件有此屬性才綁得到待人工複查
1. Invent_prime.xaml 內側仍有一個
TsControl:TsVirtualGrid(不是TsRadGridView),inner columns 已還原為TsDataGridTextColumn/TsDataGridTemplateColumn(與容器搭配正確)。但同檔另一個獨立的tsTelerik:TsRadGridView(DetailData)少設DataManager="{Binding DetailData}",仍可用但建議補上以保留 DataManager 行為。2. Pk.xaml 原始 source 有個 typo
BindingName="{Binding 結關日期}"(BindingName 接的是字串 path),E.Order worker 直接轉成DataMemberBinding="{Binding 結關日期}",runtime 行為可能改變,要確認原意。3. 6 處
Detail.Insert(index, item)改成AddItem(append)(屬於昨天 obs-to-datamanager 留下的// NOTE:標註):若DataManager<T>之後補InsertItem(int, T),這些位置可一併還原。4.
TsBinSearchColumn→TsBinSeekGridViewColumn還原時保留了FilterCondition="{Binding WarehouseFilter}"/FilterCondition="{Binding FilterCondition}",行為應等價。Build 狀態
TsERP.csproj:0 XAML 編譯錯誤、0 CS 錯誤
- 多次 build 中偶發
MC1000檔案鎖定 / mscorlib 解析警告,屬於 .NET SDK 10 MSBuild WPF MarkupCompile 工具鏈問題,與本次內容無關
連結
- 相關 skill:
.claude/skills/dg-to-radgrid
-
05-13 per-customer-config ▸
2026-05-13 — 客戶端設定檔同步機制
對應 ADR 0019。
摘要
- 新增
DatabaseListSourcePath設定,TsERP 啟動時依此路徑將客戶專屬DatabaseList.xml同步到本機目的地
- 拿掉「找不到
DatabaseList.xml時自動產生預設清單」的行為
- 新增
deploy/deploy-customer.ps1部署腳本,給 IT 人員在客戶機器一鍵建立設定檔
TsERP.SchedulerWorker同步支援此機制
新增
appsettings.json新欄位DatabaseListSourcePath(TsERP / SchedulerWorker 皆有)
Common\Environment\XmlSqlLocation.EnsureDatabaseList()靜態方法
deploy/deploy-customer.ps1:參數化部署腳本
docs/deployment/per-customer-setup.md:部署 SOP 文件
變更
XmlSqlLocation.GetSqlDataBase()不再呼叫CreateList(),僅讀檔
TsERP/App.xaml.cs:在 DI 註冊前呼叫EnsureDatabaseList(),失敗以MessageBox提示 +Shutdown(1)
TsERP.SchedulerWorker/Program.cs:同樣在 DI 註冊前呼叫EnsureDatabaseList()
刪除
XmlSqlLocation.CreateList()/EditToNewDatabase()/GetData()(含寫死的預設清單)
Breaking Changes
- 開發機若依賴自動產生的預設
DatabaseList.xml會啟動失敗。處理方式:
1. 手動在
c:/TEMPS/SqlLocation/DatabaseList.xml放開發環境清單,或2. 在
%ProgramData%\TsERP\appsettings.local.json設DatabaseListSourcePath指向 dev XML,或3. 跑
deploy/deploy-customer.ps1把開發環境當客戶處理- 新客戶部署流程改變:除了裝 TsERP 本體,需先跑部署腳本(或手動準備
DatabaseList.xml)
Migration Notes
客戶端
- 已部署、且
DatabaseList.xml已存在的客戶:啟動行為無變化
- 新客戶:請參照
docs/deployment/per-customer-setup.md
集中管理客戶設定
DatabaseListSourcePath可指向網路磁碟路徑(例:\\fileserver\TsERPConfig\客戶A\DatabaseList.xml)
- IT 在中央位置更新後,所有客戶下次啟動會依
LastWriteTime自動同步
- 新增
-
05-09 mailqueue-channel ▸
2026-05-09 — 排程信件改走 mailqueue + Mailgun(Database Mail 不再必要)
摘要
SchedulerJob加入 Phase 3DequeueMailAsync:AutoPilot SP 把組好的信件寫進新表twork_skd_mailqueue,由 SchedulerWorker 統一透過既有 Mailgun 通道寄出。客戶端 SQL Server 不再需要設定 Database Mail。新增
- 資料表
twork_skd_mailqueue:排程信件佇列(中文欄位、狀態碼 N/R/Y/F/D 對齊既有已通知/已完成風格)
- SP
TWORK_SKD_MAILQUEUE_DEQUEUE:批次撈待寄信件 + 殭屍回收(>10 分鐘卡 R 自動回 N)
- SP
TWORK_SKD_MAILQUEUE_UPDATE_STATUS:寄送結果回寫,含指數退避(30/60/120/240/480 秒)與死信判定
Procedure.TWORK_SKD_MAILQUEUE_DEQUEUE/TWORK_SKD_MAILQUEUE_UPDATE_STATUSenum
SchedulerJob.DequeueMailAsync:第三 phase,每輪 dequeue 20 筆 → Mailgun → 更新狀態
變更
IMailService.SendAsync新增 optionalisHtml = false參數;MailGunService 在isHtml=true時改用 Mailgun 的html欄位(之前只走text)
SchedulerJob.RunOnceAsync對每個 DB 從 2 phase 變 3 phase(Notify → AutoPilot → DequeueMail)
FakeMailService同步補isHtml參數
刪除
無
Breaking Changes
無:
IMailService.SendAsync是 optional 參數;mailqueue 是新增表。AutoPilot SP 內仍走EXEC msdb.dbo.sp_send_dbmail的客戶不會壞,可漸進改寫。Migration Notes
1. DBA:對每個目標 DB 跑 schema script
SqlBI/twork_skd_mailqueue.sql -- 建表 + index SqlBI/TWORK_SKD_MAILQUEUE_DEQUEUE.sql -- 出列 SP SqlBI/TWORK_SKD_MAILQUEUE_UPDATE_STATUS.sql -- 結果回寫 SP2. AutoPilot SP 改寫範本(漸進式)
把:
EXEC msdb.dbo.sp_send_dbmail @profile_name = N'qq', @recipients = ..., @subject = @subject, @body = @body, @body_format = N'HTML';換成:
INSERT dbo.twork_skd_mailqueue (收件人, 主旨, 內文, 是否HTML, 來源預存程序, 執行guid) VALUES (@recipients, @subject, @body, 'Y', OBJECT_NAME(@@PROCID), @execguid);HTML 組裝(
FOR XML PATH)整段保留,不必重寫。3. 部署順序
1. 先建新 SP / 新表
2. 再升級 SchedulerWorker
3. 一支一支改寫 AutoPilot SP(PoC 推薦從
TWORK_MOLD_SENDMAIL開始)4. 監控查詢
SELECT 狀態, COUNT(*) FROM dbo.twork_skd_mailqueue GROUP BY 狀態; -- 死信檢視 SELECT pkid, 主旨, 重試次數, 最後錯誤, 來源預存程序 FROM dbo.twork_skd_mailqueue WHERE 狀態 = 'D' ORDER BY 建立時間 DESC;相關 ADR
- 資料表
-
05-05 sql-connection-tuning ▸
2026-05-05 — SQL 連線字串補上效能與安全參數
摘要
使用者反映「連 SQL 執行命令有時候比較久」。實測冷啟動 145 ms vs 熱連線 2-3 ms,差距 50 倍。
Microsoft.Data.SqlClient4.0+ 預設Encrypt=True會在內網每次新連線多一段 TLS 交涉。本次在DataBaseParameter.ConnectionString補上Encrypt(依 Azure 旗標切換)、Min Pool Size=1、Connection Timeout=15。詳細決策見 ADR 0016。變更
Common\Environment\DataBaseParameter.cs:ConnectionStringgetter 加入:
-
Encrypt=False(地端)/Encrypt=True(Azure,依Poco.IsAzure)-
Min Pool Size=1— 確保連線池常駐至少一條熱連線-
Connection Timeout=15— 顯式寫出(雖是預設值),方便日後微調對使用者的影響
- 終端使用者:地端連線冷啟動更快;閒置一段時間後第一次操作不會再卡頓
- 管理員 / DBA:無需動 SQL Server 設定
- 開發者:若新增「公網但非 Azure」的連線目標,需要評估
Encrypt=False是否安全;目前判斷依據是DatabaseList.xml的<IsAzure>元素
不在本次範圍
DataBaseDal\PrimeConn.cs與PrimeBll\PrimeConn.cs內各自硬編的ConnectionString未一併修改。Prime 模組相關功能(佣金生成、Prime AR/AP)的連線字串仍是舊格式,若使用者使用該功能且需要相同優化,需後續另開 task
-
05-05 scheduler-log-dedicated-folder ▸
2026-05-05 — SchedulerWorker 改用 SchedulerLog,log 寫到獨立資料夾
摘要
SchedulerWorker 原本透過
Console.WriteLine(正式環境會被丟棄)與DebugLog(只在 catch 區塊呼叫)記錄歷程,導致正式環境完全看不到正常運作訊息。本次新增SchedulerLoghelper,把所有歷程統一寫到C:\temps\scheduler\yyyy-MM-dd.txt,每天一個檔。詳細決策見 ADR 0017。新增
TsERP.SchedulerWorker\SchedulerLog.cs:
- 寫到
C:\temps\scheduler\yyyy-MM-dd.txt- 兩個層級:
Info/Error- 行格式:
yyyy-MM-dd HH:mm:ss [LEVEL] 訊息- 同步輸出到 Console(保留手動執行時可見)
-
lock保護併發;try/catch吃掉 logging 失敗變更
TsERP.SchedulerWorker\SchedulerJob.cs:所有Console.WriteLine/Console.Error.WriteLine/new DebugLog().WriteLog(...)→SchedulerLog.Info(...)/SchedulerLog.Error(...)
TsERP.SchedulerWorker\Program.cs:同上
docs/admin/scheduler.md:更新所有 log 路徑說明,從c:\temps\SCHED\與C:\ERPDeploy\Scheduler\logs\統一改為C:\temps\scheduler\;新增行格式範例
Migration Notes
部署 server 端必做:
# 建立 log 資料夾並設權限 New-Item -Path "C:\temps\scheduler" -ItemType Directory -Force icacls "C:\temps\scheduler" /grant "<服務帳號>:(OI)(CI)M"資料夾不存在時
SchedulerLog會自動建立,但服務帳號必須對C:\temps\有寫入權,否則 logging 會靜默失敗。舊的
C:\temps\debuglog\內 SchedulerWorker 歷史 log 保留,未來不再寫入。對使用者的影響
- 終端使用者:無感
- 管理員:
- 首次能在正式環境看完整歷程(每個 DB 跑了什麼、處理了哪些 pkid、exit code)
- 看 log 的位置從
c:\temps\SCHED\/C:\ERPDeploy\Scheduler\logs\(先前實際也不存在,文件腐爛)改成C:\temps\scheduler\yyyy-MM-dd.txt- DBA / IT:部署需多一步建資料夾與設權限(見 Migration Notes)
- 開發者:未來在 SchedulerWorker 內加新訊息,請使用
SchedulerLog.Info/SchedulerLog.Error,不要回去用Console.WriteLine或DebugLog
-
05-04 skd-sp-split ▸
2026-05-04 — TWORK_SKD_NOTIFYEVENT 拆兩支 + 修積累 bug
摘要
TWORK_SKD_NOTIFYEVENT原本一支 SP 透過@QueryType包了「待 Email 通知」與「待 AutoPilot 自動執行」兩個無關查詢,且@QueryType=2分支累積兩個 bug(停用應是是否停用、殭屍判定還在用退役狀態碼'X')。本次拆成兩支單一職責 SP 並順手修掉。詳細決策見 ADR 0015。新增
- SP
TWORK_SKD_GET_PENDING_NOTIFY(SqlBI/TWORK_SKD_GET_PENDING_NOTIFY.sql):撈待 Email 通知事件,取代原@QueryType=1分支
- SP
TWORK_SKD_GET_PENDING_AUTOEXEC(SqlBI/TWORK_SKD_GET_PENDING_AUTOEXEC.sql):撈待 AutoPilot 自動執行事件,取代原@QueryType=2分支
Procedure.cs加 2 個對應 enum:TWORK_SKD_GET_PENDING_NOTIFY、TWORK_SKD_GET_PENDING_AUTOEXEC
變更
TsERP.SchedulerWorker/SchedulerJob.cs:
-
NotifyAsync:改打TWORK_SKD_GET_PENDING_NOTIFY,移除lp_p1="1"魔術數字-
AutoPilotAsync:改打TWORK_SKD_GET_PENDING_AUTOEXEC,移除lp_p1="2"魔術數字- 兩處
Update已通知/Update已完成(pkid, "X", ...)→"R"(對齊 ADR 0013,先前漏改)Common/DataBaseObjectEnum/Procedure.cs:舊TWORK_SKD_NOTIFYEVENT標[Obsolete],註解指向 ADR 0015
- SP
TWORK_SKD_GET_PENDING_AUTOEXEC內 bug 修復(相對於原@QueryType=2):
-
停用 <> 'Y'→是否停用 <> 'Y'(對齊Twork_skd_vw_eventitemModel.是否停用)-
已完成 <> 'X'→已完成 <> 'R'(殭屍判定恢復作用)-
DATEADD(MINUTE, -10, GETDATE())→DATEADD(MINUTE, -10, @datetime)(基準時間可由 caller 控制)刪除
- 無 — 舊 SP
TWORK_SKD_NOTIFYEVENT留在 DB 與 enum 中,標[Obsolete]漸進淘汰(Migration Notes 第 4 步說明何時可 drop)
Breaking Changes
- DB schema 必動:兩支新 SP 必須先在 DB 建立才能升 SchedulerWorker,否則 worker 跑不起來
- 殭轉判定真的會生效:先前
'X'bug 等於沒判殭屍;現改'R'後,超過 10 分鐘還在執行中的事件會被視為前一輪 crash 而重撈。若 production 真有合法跑超過 10 分鐘的事件,會被重複撈 → 重複跑。建議先觀察一輪執行開始時間分布,視情況調大殭屍 timeout(目前 hardcode 10 分鐘)
- 直呼舊 SP
TWORK_SKD_NOTIFYEVENT的外部腳本 / 排程不受影響(SP 還在),但建議遷移到新 SP
Migration Notes
DBA 端(每個目標 DB 必跑)
-- 1. 建立待通知 SP 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 GO -- 2. 建立待自動執行 SP 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 GO完整 script 在
SqlBI/TWORK_SKD_GET_PENDING_NOTIFY.sql與SqlBI/TWORK_SKD_GET_PENDING_AUTOEXEC.sql。部署順序
1. DBA 先建新 SP(上述兩個
CREATE PROCEDURE)2. Build & deploy 新版
TsERP.SchedulerWorker3. 觀察 1-2 輪正常運轉:看
c:\temps\SCHED\內 log 有Notify pkid=.../AutoPilot pkid=...紀錄4. 可選清理:確認穩定後
DROP PROCEDURE TWORK_SKD_NOTIFYEVENT順序顛倒會在升級 gap 期間 SchedulerWorker 找不到 SP 而 crash。
對使用者的影響
- 終端使用者:無感,UI / 行為不變
- 管理員:殭屍事件回收機制現在真的有作用 — 若有事件卡在
R狀態超過 10 分鐘會被視為前一輪 crash 重撈
- DBA:必跑兩個
CREATE PROCEDUREscript
- 開發者:撈待辦事件不再透過
@QueryType魔術數字,新增類似查詢時直接命名新 SP 即可
- SP
-
05-04 rightcalendar-ux-overhaul ▸
2026-05-04 — RightSideCalendar UX 全面改造(圓點著色 / 過期紅色 / 已完成灰階 / 按鈕下排 / async 月快取)
摘要
接續同日
2026-05-04-rightcalendar-event-dot-fix.md的事件圓點修復,本次再做一輪視覺與資料層的改進:日曆圓點按擁有者類別著色、過期未完成事件標紅、已完成事件灰階加刪除線、4 顆操作按鈕從事件右側移到下排,事件清單寬度大幅增加;資料層改 async + 月份快取,切月份不再卡 UI、同月切換不重抓 server;ViewModel 的 4 個Show*屬性整併成OwnerFilterscollection,新增擁有者類別不必再 patch 4 個屬性。詳細決策見 ADR 0014。
新增
ViewModel/MainWindowPage/OwnerFilterItem.cs:擁有者篩選 item POCO(OwnerType/DisplayName/IsVisible/IsChecked)
CalendarPageViewModel.OwnerFilters:ObservableCollection<OwnerFilterItem>,4 顆 toggle 的 binding 來源
CalendarPageViewModel.EventSummaryByDate:Dictionary<DateTime, DayEventSummary>,每日Count+DominantOwnerType,給日曆圓點著色用
CalendarPageViewModel.HasNoEventsToday/HasFilteredOutAll:空狀態分流;前者是「該日完全無事件」,後者是「該日有事件但全被 toggle 篩掉」
CalendarPoco.已過期未完成:衍生屬性(預定時間 < Now && 已完成 != "Y"),給事件 row 標紅用
EventDayTemplateSelector新增 4 個 owner 變體 template:EventTemplatePersonal/Department/Company/System
RightSideCalendar.xamlUserControl.Resources新增 9 個 brush:EventDatePrimaryBrush/EventOwnerSecondaryBrush/EventItemBackgroundBrush/EventEmptyHintBrush/EventOverdueBrush/OwnerColorPersonal/OwnerColorDepartment/OwnerColorCompany/OwnerColorSystem
- 內部欄位
_monthCache: Dictionary<(int Year, int Month), List<CalendarPoco>>:client-side 月份事件快取
- 新增
LoadEventsForMonthAsync(DateTime, bool)private method 取代舊 sync 版
變更
ViewModel/MainWindowPage/CalendarPageViewModel.cs:
- 移除
Show個人 / Show部門 / Show公司 / Show系統屬性與對應欄位- 移除
_show系統UserSet旗標、CanSee系統唯讀屬性、EventDates(HashSet<DateTime>)- 移除 public
GetEvent(string date)(唯一外部呼叫已被註解,改全部走LoadEventsForMonthAsync)- 移除
LoadEventsForMonth同步版,改LoadEventsForMonthAsync-
IsOwnerVisible從 byte switch (0/1/2/9) 改成查OwnerFilters,magic number 消失-
RefreshUserPermission簡化:只看IsAdmin()變化來決定要不要同步系統 toggle 的IsVisible/IsChecked,不再用_show系統UserSet旗標-
DisplayDate.set/Refresh()用_ = LoadEventsForMonthAsync(...)fire-and-forget- 例外處理改為
try/catch + TsMsgBox.ShowError包在LoadEventsForMonthAsync內,避免 fire-and-forget 例外被吞掉-
HasNoEvents拆成HasNoEventsToday+HasFilteredOutAllLogicBll/Calendar/CalendarPoco.cs:
-
已完成Binding.set連帶通知已過期未完成屬性變更(取消勾選後紅色立即恢復)TsControl/Calendar/EventDayTemplateSelector.cs:
- 從原本 1 個
EventTemplate加 fallback 變成 5 個(4 種 owner + fallback gray)-
SelectTemplate改用vm.EventSummaryByDate查DominantOwnerType,再透過vm.Context.EnumManager.EventOwner把 byte 對應到 enumTsControl/Calendar/RightSideCalendar.xaml:
- 4 個獨立
TsToggleButton改成ItemsControl綁OwnerFilters+UniformGrid Columns=4+DataTemplate- 4 顆操作按鈕(導航 / 執行 / 附件 / 報告)從事件 row 右側
Grid.Column=2移到下排Grid.Row=1,用UniformGrid Columns=4等寬排列-
EventControlDataTemplate 內:根 Grid 已完成時Opacity=0.5、EventDate過期未完成 Foreground 變紅、EventTitle已完成加TextDecorations="Strikethrough"- 新增 5 個 EventTemplate XAML block 對應 4 種擁有者類別 + 1 fallback
- 硬編色(
#FF000000/#FF686868/#FFEBEBEB/#FF888888)改用StaticResource引用UserControl.Resources內的 brush- 空狀態 TextBlock 拆兩個:「今日無事件」綁
HasNoEventsToday、「事件已被篩選」綁HasFilteredOutAllViewModel/Common/DataNavigateHelperV2.cs:
- 順手清掉先前診斷 row scroll 問題暫加的
Debug.WriteLine(無功能影響)刪除
CalendarPageViewModel.GetEvent(string date)public method(唯一 caller 已被註解,無實際使用)
CalendarPageViewModel.EventDates(HashSet<DateTime>) 屬性(被EventSummaryByDate取代)
CalendarPageViewModel.Show個人 / Show部門 / Show公司 / Show系統屬性與對應 fields
CalendarPageViewModel.CanSee系統屬性
CalendarPageViewModel._show系統UserSet旗標
CalendarPageViewModel.HasNoEvents屬性(拆成兩個)
Breaking Changes
- 任何外部 XAML / code 若 binding 過
Show個人 / Show部門 / Show公司 / Show系統 / CanSee系統 / EventDates / HasNoEvents,會 binding error。grep 後本 repo 內無此 caller。
CalendarPageViewModel.GetEvent(string)已移除;外部呼叫者請改用Refresh()。本 repo 內無 caller。
Migration Notes
- 無 SQL / 設定變更
- 多語系:新增一條 key
事件已被篩選;未設定時 fallback 到中文字面值,不會 crash
- 顏色與資源:所有 brush 集中在
RightSideCalendar.xaml的UserControl.Resources,未來若要抽到 theme dictionary 一處改即可
對使用者的影響
- 切月份順了:DB 抓資料推到背景 thread,UI thread 不再被卡;同月份切回來不重抓 server
- 過期事件醒目:日期已過但未勾「已完成」的事件,事件 row 上方時間文字會變紅
- 已完成事件視覺淡化:整個 row
Opacity=0.5、標題加刪除線;列表上一眼分辨「已處理」與「待處理」
- 日曆圓點上色:每日事件圓點顏色反映「該日事件最多者」的擁有者類別 — 橘=個人、綠=部門、藍=公司、深灰=系統。掃過月曆能看出主要事件來源
- 事件文字變寬:4 顆操作按鈕從右側移到下排,事件標題與說明的可用寬度從約 60px 增加到約 180px,內容不再被截
- 空狀態分流:當天若資料庫真的無事件,顯示「今日無事件」;若有事件但全被 4 個擁有者 toggle 篩掉,改顯示「事件已被篩選」,避免使用者誤以為資料消失
- 管理員系統 toggle 行為更直觀:admin 第一次開日曆會把「系統」toggle 設成可見且勾選;admin 在 session 內手動取消勾選後,後續
Refresh()不會再被打回去(除非IsAdmin()權限本身變化)
-
05-04 rightcalendar-event-dot-fix ▸
2026-05-04 — RightSideCalendar 事件圓點修復、擁有者篩選與重構
摘要
右側
RightSideCalendar的當日事件圓點之前只有時間正好是 00:00:00 的事件才顯示,其他時間的事件會被當作「無事件」漏掉。同時CalendarPageViewModel的屬性 setter 內含 DB 抓取與 filter 副作用,月切換每格都跑 LINQ。順手把死碼清掉、加上個人 / 部門 / 公司 / 系統四類擁有者的多選 toggle 篩選(系統需 admin 權限),並加上空狀態提示與錯誤處理。新增
- 擁有者類別篩選:右側日曆下方 4 顆 ToggleButton(個人 / 部門 / 公司 / 系統),多選;預設個人+部門+公司開啟、系統依
Context.User.IsAdmin()決定是否顯示
CalendarPageViewModel.Show個人 / Show部門 / Show公司 / Show系統:篩選 toggle binding 屬性
CalendarPageViewModel.CanSee系統:唯讀屬性,控制系統 toggle 的顯示與可勾性,等於IsAdmin()
CalendarPageViewModel.HasEvents / HasNoEvents:給「今日無事件」空狀態提示用的衍生屬性
CalendarPageViewModel.EventDates:HashSet<DateTime>,在Events被指派時一次算好;給EventDayTemplateSelectorO(1) 查詢用
- 內部欄位
_eventsByDate(ILookup<DateTime, CalendarPoco>):以日為鍵預先 group,SelectedDate變更時 O(1) 過濾
- 「今日無事件」空狀態文字:當所選日期 / 篩選條件下無事件時顯示
變更
TsControl/Calendar/EventDayTemplateSelector.cs:
- 從
Events.Select(...).Any(x => x.預定時間 == content.Date)改為vm.EventDates.Contains(content.Date.Date)- 順帶修掉
預定時間(含時分)對content.Date(午夜)的不對等比較 → 以.Date正規化為日比對TsControl/Calendar/RightSideCalendar.xaml:
- ListBoxItem 內 7 處
ElementName=Grid, Path=DataContext.XxxCommand→ 改用RelativeSource AncestorType=UserControl,不再依賴 root grid 命名- 新增 4 顆
TsToggleButton篩選列(UniformGrid 4 欄)- 新增「今日無事件」空狀態 TextBlock,與 ListBox 互斥顯示
- 移除死碼:
StepTemplate、TaskStepListBoxStyle、EventCardTemplateSelector整段ViewModel/MainWindowPage/CalendarPageViewModel.cs:
-
Events.set內預建_eventsByDate與EventDates,並觸發RefreshFiltered()-
SelectedDate.set移除 LINQ filter;改呼叫RefreshFiltered()-
DisplayDate.set移除if (NeedGetEvents) ...內聯邏輯;改呼叫LoadEventsForMonth(value, force:false)- 新增
RefreshFiltered()/LoadEventsForMonth(monthDate, force)/IsOwnerVisible(poco)三個 private method-
Refresh()從 hack 寫法 (DisplayDate = default; DisplayDate = date;) 改為LoadEventsForMonth(date, force:true)-
已完成Execute/NavigateExecute包 try/catch +TsMsgBox.ShowError;已完成Execute失敗時還原 UI 勾選避免 UI/DB 不一致- 兩個 setter 改為只有值變動才觸發(加
if (_xxx == value) return;),減少不必要的 PropertyChanged- 移除未被任何 XAML 使用的
EventText、LayoutText屬性 與QueryCommandLogicBll/Calendar/CalendarPoco.cs:
-
Get擁有者類別描述()從 if/else 寫死 (0/1/2) 改成委派給Context.EnumManager.EventOwner.GetEventOwner(...).Description,正確處理9 = 系統(之前回空字串)刪除
TsControl/Calendar/EventCardTemplateSelector.cs整檔(XAML 已不再 reference,本次連 selector 一起拿掉)
Breaking Changes
- 無;
Events/SelectedDate/DisplayDate/Refresh()公開介面與行為均維持相容
Refresh()仍會把SelectedDate對齊月初(與原版一致),外部呼叫者 (EventSelfViewModel.OnAfterAudit、EventProjectViewModel.AfterMoverRecord) 行為不變
Migration Notes
- 無 SQL / 設定變更,純 .NET 端重構
- 多語系:toggle 文字以
DisplayMeta[個人 / 部門 / 公司 / 系統]取得,未設定時 fallback 到中文字面值
對使用者的影響
- 圓點正確顯示:右側日曆中,只要該日有事件(不論時間是幾點),日期格底下都會出現灰色圓點。原本只有 00:00 事件才顯示
- 月份切換較順:日曆 render 從 O(N×天數) LINQ 降到 O(1) HashSet 查表
- 可依擁有者篩選:4 顆 toggle 自由切換顯示哪幾類事件
- 管理員專屬:「系統」toggle 僅 admin 可見可選;非 admin 即使資料有系統事件也不會顯示在清單
- 空狀態提示:當天無事件(或被篩選掉)時顯示「今日無事件」
- 錯誤可見:勾選「已完成」或點「導航」失敗時顯示錯誤訊息,不再吞掉
- 擁有者類別篩選:右側日曆下方 4 顆 ToggleButton(個人 / 部門 / 公司 / 系統),多選;預設個人+部門+公司開啟、系統依
-
05-03 remove-commpr2-org-titles ▸
2026-05-03 — 移除 commpr_2 的「單位」「職稱」欄位
摘要
從
Twork_com_vw_commpr_2Model移除單位與職稱兩個欄位,並同步清理上下游:兩個 Source、TodoListPoco 衍生屬性、Emailsender 頁面欄位、多語系設定。刪除
Twork_com_vw_commpr_2Model.單位Twork_com_vw_commpr_2Model.職稱兩個 string property
Twork_com_vw_commpr_2Source與WorkflowStepSource對外曝露的單位職稱property wrapper
TodoListPoco.OrganizationTodoListPoco.Titles衍生屬性與其在SetData(Twork_com_vw_commpr_2Source)/SetData(WorkflowStepSource)中的賦值
Emailsender.xaml中 detail grid 的「單位」「職稱」兩欄
- 多語系:
Common/Lang/Xaml/Emailsender.json與Common/Lang/Model/Twork_com_vw_commpr_2.json內對應 key
變更
無(本次純刪除,無功能調整)。
新增
無。
Breaking Changes
- 凡讀取
Twork_com_vw_commpr_2Source.單位/.職稱、WorkflowStepSource.單位/.職稱、或TodoListPoco.Organization/.Titles的外部程式碼會編譯失敗。
- DB view
twork_com_vw_commpr_2端若仍保留這兩個欄位,目前 Dapper 反射映射會忽略它們,不會炸;建議由 DBA 視情況決定是否同步移除。
Migration Notes
若有外掛或自訂程式需要顯示處理人的單位/職稱,請改從
Context.DescriptionHelper.GetPerdata(處理人編號)即時查詢人事檔取得。 -
05-03 eventitem-status-r-completion-time ▸
2026-05-03 — EventItem 狀態:X → R + 完成時間自動寫入
摘要
twork_skd_eventitem的「執行中」狀態碼從'X'(視覺上像失敗)改為'R'(Running)。已完成 = 'Y'時 SP 自動寫完成時間 = GETDATE()。AutoPilotHelper.ExecuteMethod補完整生命週期:執行前標'R'、成功標'Y'、失敗標'F',這樣執行開始時間 / 完成時間才會被填。詳細決策見 ADR 0013。新增
- 無新增類別;只是把既有的
Twork_skd_vw_eventitemModel.完成時間欄位真的開始用
變更
AutoPilot/AutoPilotHelper.cs::ExecuteMethod:
- 執行前
bll.Update已完成(pkid, "R", DateTime.Now)(標執行中 + 寫 執行開始時間)- 成功 +
執行後完成 == "Y":bll.Update已完成(pkid, "Y")—— SP 收到後自動寫完成時間 = GETDATE()- 失敗:
bll.Update已完成(pkid, "F")- 快取
bll變數,避免重複new CalendarBll(Context)- SQL
TWORK_SKD_UPDATE_EVENT_STATUSprocedure:
-
已完成分支多寫[完成時間] = CASE WHEN @LP_P3 = 'Y' THEN GETDATE() ELSE [完成時間] END- 加
[是否停用] = CASE WHEN @LP_P3 = 'Y' THEN 'Y' ELSE '' END同步行為-
LP_P3預期值文件從'X' / 'Y' / 'F' / ''改為'R' / 'Y' / 'F' / ''LogicBllTests/Calendar/CalendarBllTests.cs:
- 所有
"X"字面 →"R"-
LP_P1assertion:已通知系列改"1"、已完成系列改"2",與目前CalendarUpdateTypeenum 值對齊(已通知=1, 已完成=2)- 測試方法名
..._status_X_...→..._status_R_...docs/admin/scheduler.md:狀態碼表格與範例 SQL 同步更新
刪除
- 無
Breaking Changes
- 狀態字面
'X'退役:production 端 grep 過後沒有 active caller 寫'X',所以技術上沒有任何 client 程式會壞。但若有外部系統(手寫 SQL update、其他程式直接寫 DB)使用'X'標執行中,需配合改為'R'。
Migration Notes
1. SQL Server 端必跑:升級
TWORK_SKD_UPDATE_EVENT_STATUSprocedure```sql
ALTER 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 [twork_skd_eventitem]
SET [已通知] = @LP_P3,
[執行開始時間] = CASE WHEN @LP_P4 = '' THEN [執行開始時間] ELSE @StartTime END
WHERE [pkid] = @LP_P2;
ELSE IF @LP_P1 = '2'
UPDATE [twork_skd_eventitem]
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
```
2. 歷史資料相容(可選):若資料庫殘留
已完成 = 'X'的 row,可一次性轉換:```sql
UPDATE twork_skd_eventitem SET 已完成 = 'R' WHERE 已完成 = 'X';
```
不轉換也不會壞,只是畫面會繼續顯示
X。3. SchedulerWorker / TsERP 升級至此 commit 後,新觸發的 EventItem 才有
執行開始時間 / 完成時間值。歷史資料一律 NULL,無法回填。對使用者的影響
- EventItem 列表新狀態:
R(Running)取代X,視覺上不再像「失敗 / 取消」
- 完整時間軸有資料:
執行開始時間在每次排程觸發時自動填、完成時間在事件成功時自動填
- 失敗事件可分辨:scheduler 跑失敗的事件
已完成 = 'F',可從 EventProject 列表一眼看出
- EventResultView 模態(同日 2026-05-03 changelog)的 header 看到的時間欄位開始有實際值
- 無新增類別;只是把既有的
-
05-03 eventitem-result-viewer ▸
2026-05-03 — EventItem 執行結果檢視畫面 + AutoPilot 執行GUID
摘要
EventItem(行事曆事件項目,由AutoPilotHelper.ExecuteMethod觸發)跑完後,使用者現在可以從Eventproject明細列右鍵「查看執行結果」,開啟一個動態欄位的檢視畫面,看到該次執行寫進結果表的列。為了支援上述查看,
AutoPilotHelper在每次執行 task 前會自動產生「執行GUID」、寫回 EventItem 的執行guid欄位,並把 task JSON 內所有"{guid}"token 替換成實際 guid(opt-in:只有 template 寫了"{guid}"的欄位才會被替換)。新增
Common/AutoPilot/SchedulePlaceholders.cs:
- 新常數
ExecutionGuid = "{guid}",與既有的Today = "{d}"/PreviousMonthEnd = "{-d1}"同一套 token 模式- 新方法
ReplaceExecutionGuidTokens(JObject json, string executionGuid):把 JSON 頂層 string 屬性內的{guid}換成實際 guidLogicBll/Calendar/CalendarBll.cs::UpdateExecutionGuid(int pkid, string executionGuid, out string msg):read-modify-write EventItem.執行guid(沿用同檔Update()的AdeUploadBll模式)
ViewModel/L.Communication/EventResultViewModel.cs:modal VM,繼承ERPProgramContentWindowViewModel,建構式收IAppContext + Twork_skd_vw_eventitemSource。Load 時走Log_Sys_Exec+Procedure.DARB_GETDATA4,condition執行guid = '<guid>',從 EventItem 的結果表撈整列回來,bindable 為DataTable ResultTable
TsERP/L.Communication/EventResultView.xaml+.xaml.cs:<TsControl:TsView>根,上方 5 個 metadata label(執行方法 / 結果表 / 預定時間 / 執行開始時間 / 執行GUID),下方<telerik:RadGridView AutoGenerateColumns="True" IsReadOnly="True" ItemsSource="{Binding ResultTable}" />
Eventproject.xamldetail TsRadGridView 加 row context menu「查看執行結果」
變更
AutoPilot/AutoPilotHelper.cs::ExecuteMethod:執行前Guid.NewGuid()→CalendarBll.UpdateExecutionGuid(item.pkid, guid)→SchedulePlaceholders.ReplaceExecutionGuidTokens(json, guid)。GUID 是框架統一管的,所有 task type 都會生成;JSON 替換只動明確寫"{guid}"的欄位
AutoPilot/AutopilotMethod_LogSysExec.cs:DefaultParameter["LP_P1"]預設值改為SchedulePlaceholders.ExecutionGuid(即"{guid}");doc summary 說明 token 用法
ViewModel/L.Communication/EventProjectViewModel.cs:加ICommand ViewResultCommand+ViewResultExecute(),handler 開EventResultViewModelmodal;空執行guid/結果表/ 沒選 row 時跳訊息
TsERP/App.xaml:加<DataTemplate DataType="{x:Type communication:EventResultViewModel}"><schedule:EventResultView /></DataTemplate>
刪除
- 無
Breaking Changes
- 無(沒有覆蓋使用者既有設定的行為;GUID 替換是 opt-in token)
Migration Notes
1. 既有 EventItem 的
執行guid欄位都是空的:本次升級之前跑過的歷史紀錄,沒有 guid 可查。檢視畫面會顯示「該事件無執行紀錄,請先執行排程」。沒辦法回填。2. 想接 GUID 的使用者建立的 LogSysExec 排程:在想接 guid 的 LP_Px 欄位填字面值
{guid}(或留 default 的 LP_P1)。框架在執行那一刻會替換成實際 guid。沒填{guid}的欄位完全不會被改動。3. 新建/維護中的 SQL Procedure:要走「LogSysExec → 寫結果表 → viewer 撈」流程的 procedure,請確認:
- 對應的
結果表有執行guid欄位(typevarchar(36)或nvarchar(36))- Procedure body INSERT 結果列時把對應
LP_Px(接{guid}那個)寫進執行guid欄位4. EventItem.結果表 由使用者建 EventItem 時填:框架不會自動產生這個值,是 EventItem template 的設定。
對使用者的影響
- 新功能:在「事件項目」頁面(Eventproject)的明細列上右鍵就能看到「查看執行結果」。點下去開模態視窗,顯示該次執行的 metadata(執行方法 / 結果表 / 時間 / GUID)+ 結果表撈出來的對應列
- 欄位是動態自動產生的:依
結果表的 schema 即時組欄位;read-only,不能直接編輯
- 沒跑過的事件:右鍵點下去會跳訊息「該事件無執行紀錄,請先執行排程」
- GUID 對使用者是顯式 opt-in:在 template 參數放
{guid}才會收到;不放就跟之前完全一樣
-
05-03 autopilot-task-unify ▸
2026-05-03 — IAutoPilotTask 收攏參數產生與顯示
摘要
把排程「執行」與「參數產生」兩條原本互相不知道對方存在的程式碼路徑收攏到
IAutoPilotTask一個介面下。AutoPilotGenerator廢除,參數格式統一改 JSON,SelectedMethod改成從 registry 反查、setter 補設執行方法。詳情見
docs/decisions/0012-autopilot-task-unify.md。新增
IAutoPilotTask.BuildScheduledParameter(JObject, DateTime)—— 把 template 的參數骨架(含{d}/{-d1}placeholder)展開成 eventitem 的實際參數。
IAutoPilotTask.FormatDisplay(JObject)—— 給eventitem.參數顯示用的人話字串。
Common/AutoPilot/SchedulePlaceholders.cs—— 集中{d}、{-d1}的解析。
- 三個新 task:
Bcurrenm_FxRate、Pivot_Get、Word_MailMerge。AutoPilotRegistry現在註冊全部 7 個 task(原本只註冊 1 個)。
變更
IAutoPilotTask子類的Name屬性統一改用英文 C# 識別字風格:
-
SliyAutoPilot_CloseBook.Name"關帳"→"CloseBook"-
Balance_Balance.Name"CreateBalanceSheet"→"BalanceReport"-
Paymntpq_Query.Name"CreateEmail"→"PaymntpqQuery"-
SliyAutoPilot_CloseBook.ProgramClass修為Common.ProgramClass.SLIY.Name(原為 null)。- 中文顯示走多語系
DisplayName,key 是ProgramClass.AUTOPILOTMETHOD。Twork_skd_vw_eventtemplatedSource.SelectedMethod:
- getter 從
_registry.GetTask(單據別, 執行方法)反查(原本只回傳快取欄位)。- setter 補設
執行方法 = value.Name,原本只設單據別與參數。- setter 改走
原始Jsonsetter(與TranslateJson()同一條路),不再有兩條互相打架。-
單據別/執行方法setter 加上OnPropertyChanged(nameof(SelectedMethod))。Twork_skd_vw_eventtemplatedSource.CreateEventItem改用task.BuildScheduledParameter+task.FormatDisplay。
刪除
Model/AutoPilotGenerator.cs整支移除。
AutoPilot/AutoPilot.cs整支移除;FxRate/GetPivot/GetWordMailMerge三個 method 的邏輯 inline 進對應 task 的Execute。
AutoPilot/AutoPilotHelper.cs內的AutoPilotResult死碼私有 method 與註解掉的 CSVExecuteMethodoverload 一併清掉。
AutoPilotTask.GetParameterFormula移除(原本宣告JObject卻沒 return 的壞掉 stub)。
Breaking Changes
1. DB 中
執行方法欄位的值需要更新。原本存關帳、CreateEmail、CreateBalanceSheet等舊值的 row,registry lookup 會 miss,排程不會跑。2.
參數欄位 CSV 格式變 JSON。原本"Y,2026/05/03"這種要改成{"CloseType":"Y","CloseDate":"2026/05/03"}。Migration Notes
UPDATE twork_skd_vw_eventtemplated SET 執行方法 = 'CloseBook' WHERE 執行方法 = '關帳'; UPDATE twork_skd_vw_eventitem SET 執行方法 = 'CloseBook' WHERE 執行方法 = '關帳'; UPDATE twork_skd_vw_eventtemplated SET 執行方法 = 'BalanceReport' WHERE 執行方法 = 'CreateBalanceSheet'; UPDATE twork_skd_vw_eventitem SET 執行方法 = 'BalanceReport' WHERE 執行方法 = 'CreateBalanceSheet'; UPDATE twork_skd_vw_eventtemplated SET 執行方法 = 'PaymntpqQuery' WHERE 執行方法 = 'CreateEmail'; UPDATE twork_skd_vw_eventitem SET 執行方法 = 'PaymntpqQuery' WHERE 執行方法 = 'CreateEmail';參數欄位 CSV → JSON 建議讓使用者進排程模板編輯介面重新選一次「執行方法」載入新的預設骨架(DefaultParameter)後再填值。多語系
AUTOPILOTMETHOD多語系檔需要補上新Name對應的中文翻譯(CloseBook→關帳、BalanceReport→資產負債表等),讓 ComboBox 顯示維持中文。 -
05-03 addevent-task-picker ▸
2026-05-03 — 排程模板:合併「單據別 + 執行方法」並加上參數提示
摘要
AddEventContent.xaml的「自動化參數」區塊原本要使用者先選「單據別」再選「執行方法」兩個下拉選單,現整併成一個「執行項目」下拉,顯示文字為「程式名稱 - 方法名稱」。同時在「參數」TextBox 下方加上占位符說明,讓使用者直接看到{d}/{-d1}等可用 token,不必去翻SchedulePlaceholders原始碼。新增
Twork_skd_vw_eventtemplatedSource.AutoPilotTaskOptions— 把 registry 內所有IAutoPilotTask與使用者權限可瀏覽的ProgramClass做交集,包成AutoPilotTaskOption(含Display = "{程式名稱} - {方法 DisplayName}")。
Twork_skd_vw_eventtemplatedSource.SelectedTaskOption— 提供給合併後的 ComboBox 雙向綁定;setter 直接轉發到SelectedMethod,由原有 setter 同步寫入單據別/執行方法/原始Json。
- 「參數」TextBox 下方新增黃底提示框,列出占位符對照表,並動態顯示目前選定方法的
Description(例:「本項目:關帳」)。
- 參數 TextBox 加上
AcceptsReturn=True與FontFamily=Consolas,讓 JSON 多行顯示與閱讀更容易。
變更
- 「單據別 / 執行方法」兩個下拉合併為「執行項目」一個下拉。
- 「執行後完成」「是否自動執行」整理成同一列,標籤改為「執行旗標」並改用 CheckBox 內建 Content 顯示文字。
刪除
無(
AutoPilotProgramClassList/AutoPilotMethodList/SelectedMethod/UpdateMethodListByProgramClass仍保留,內部CreateEventItem流程未變動)。Breaking Changes
無。
單據別與執行方法仍以原本格式寫入 DB;只是 UI 改成由單一下拉一次設定。Migration Notes
無需 DB 或設定檔遷移。
- 既有資料的
單據別/執行方法載入後會自動 round-trip 為對應的SelectedTaskOption。
- 若 registry 內某個 task 的
ProgramClass不在使用者的權限表內,該選項不會出現在下拉中(與舊版「單據別必須先在權限表」一致)。
-
05-02 readme-setup ▸
2026-05-02 — 新增 README 與新機器初始設定指引
摘要
repo 根目錄新增
README.md,集中記錄新機器(含 CI)首次設定 Telerik 私有 NuGet 認證、Telerik 授權檔位置、Telerik MCP tools 安裝步驟。同時把.tools/加入.gitignore。新增
README.md:新機器初次設定流程
- 推薦用
NuGetPackageSourceCredentials_telerik環境變數(Username=...;Password=...)配置 Telerik NuGet 憑證,而非寫進NuGet.Config或dotnet nuget update source --store-password-in-clear-text- Telerik 授權檔位置
%APPDATA%\Telerik\telerik-license.txt-
dotnet tool update --tool-path ./.tools Telerik.WPF.MCP/Telerik.Documents.MCP安裝指令- 常用 build / test / publish / mkdocs 指令彙整
變更
.gitignore:新增.tools/,避免把 dotnet local tool 的二進位與dotnet-tools.json帶進版控
對使用者的影響
- 終端使用者無感
- 開發者 / 新進工程師:新機器只要設一次 user-level 環境變數
NuGetPackageSourceCredentials_telerik,就能 restore Telerik 套件、安裝 MCP tools;不需動NuGet.Config,可避免不小心把帳密 commit 進 repo
- CI/CD:同樣以 secret env var 注入
NuGetPackageSourceCredentials_telerik即可,不需另外建立 credential file
Migration Notes
舊有開發機若先前已透過
dotnet nuget update source telerik --username ... --password ... --store-password-in-clear-text把帳密寫進%AppData%\NuGet\NuGet.Config,建議:1. 設定 user-level 環境變數
NuGetPackageSourceCredentials_telerik2. 從
%AppData%\NuGet\NuGet.Config移除<packageSourceCredentials>區塊內的 telerik 條目3. 重開終端確認
dotnet restore仍能通過無此需求者不必處理。
-
05-02 bom-flow-tree-tab ▸
2026-05-02 — Bom_flow 新增「製程明細(樹狀)」tab + 修 RadDiagram 不顯示
摘要
D.Bom\Bom_flow多一個 tab「製程明細(樹狀)」,原 tab 不動;同時修掉「流程圖圖形」tab 內 RadDiagram 因樣式覆寫導致連線/節點不顯示與一連串 binding error 的問題。新增
- 新 tab「製程明細(樹狀)」(
DisplayMeta[製程明細(樹狀)]),位置:原「製程明細表」之後、「多階材料用量清單」之前
- 用
telerik:RadTreeListView+TreeListViewTableDefinition以組件編號 → 品號自連結建階層- 顯示欄位:品號 / 品名 / 生產部門 / 部門簡稱 / 數量 / 單位 / 損耗率 / 標準工時 / 整備工時 / 移轉天數 / 注意事項 / 生效日期 / 失效日期 / 零件編號 / 顯示序號 / 組件編號
DisplayProcessingPoco.Children屬性(in-memory,給 tree 用,不參與 DB mapping)
Bom_flowViewModel.DisplayProcessingTreeRoots屬性 +BuildTree()私有方法,跟SelectedPartNo連動
變更
RadDiagram.ShapeStyle/ConnectionStyle加BasedOn="{StaticResource {x:Type telerik:RadDiagramShape/Connection}}",保留主題 ControlTemplate
- 修掉 4 條
EndBezierPoint / StartPoint / StartBezierPoint / EndPoint對LineSegment.Point/PathFigure.StartPoint的 binding error- 修掉「流程圖圖形」tab 內 RadDiagram 沒畫出節點/連線的問題
2026-05-03 後續
- 樹狀 tab 把查詢組件本身當成第一層唯一根節點,原本的第一層子項全部掛在它底下,便於整體展開檢視
- 為了能「合成」一個根節點,
DisplayProcessingPoco多一個只吃IAppContext的建構子(原(TMP製程表, IAppContext)不變)-
BuildTree多吃一個IAppContext參數刪除
- 無(原「製程明細表」tab 與其雙擊展開邏輯完全保留)
Breaking Changes
- 無
Migration Notes
- 終端使用者:自動多一個 tab,無操作變更
- 開發者:新 DisplayMeta key
製程明細(樹狀)兩份 JSON 都已更新
-
Common\Lang\Xaml\Bom_flow.json-
TsERP\Lang\Xaml1\Xaml.json- 重 build 前要先關掉執行中的
TsERP.exe,否則bin/Debug/net8.0-windows/*.dll會被鎖住複製失敗(與本次改動無關,是 dev 環境常見現象)
- 新 tab「製程明細(樹狀)」(
📅 2026 年 4 月 (10 篇)
-
04-29 batchedit-itemsbindingmanager ▸
2026-04-29 — BatchEditBll 改用 ItemsBindingManager + EF ChangeTracker
摘要
BatchEditBll(批次編輯 BLL 抽象基底;目前唯一實作為SecretBll,用於權限批次設定)從原本的「adeDataBackup自製 diff +AdeUploadBll.UpdateData」改為「ItemsBindingManager集中管理 + EF ChangeTracker 偵測變動 +UploadToSql」。詳細決策見 ADR 0010。新增
Model/Source/A.SystemData/PrgsctBatch.cs:從LogicBll/Other/Secret/PrgsctBatch.cs搬過來(namespace 改Model),兩個 in-memory only 屬性使用者代號/使用者姓名加[NotMapped]
Model/LocalDbContext.cs新增DbSet<PrgsctBatch> PrgsctBatch:OnModelCreating用HasBaseType((Type)null).ToTable("PrgsctBatch")跟父類別 entity 解綁 + 對到獨立 SQLite 表PrgsctBatch
DataManager<T>.SeedFromRemote(IEnumerable<T> data):取代/重寫先前的DirectSaveToSqliteAndLoad;主動清IsDirty=false/IsNew=false/增刪修=""後 upsert 到 SQLite 並Load()接管 EF tracker
變更
LogicBll/Ade/Transfer/BatchEditBll.cs:
- 抽象屬性
IList[] EditSource→ItemsBindingManager-
Save()流程:UpdateBefore→SetUploadData(保留為 hook,預設無動作)→ItemsBindingManager.SaveAll()→ItemsBindingManager.UploadToSql(Status, out msg)- 移除壞掉的
adeDataBackup.DataBackup/adeDataBackup.GetUpdateData殘留LogicBll/Other/SecretBll.cs:
- 改實作
protected sealed override ItemsBindingManager ItemsBindingManager { get; }- 新增
private readonly DataManager<PrgsctBatch> prgsctData-
PrgsctBatchproperty 從List<PrgsctBatch>(auto-property)改為IList<PrgsctBatch>直接回傳prgsctData.ObservableCollection(live view)-
SqlQuery改用prgsctData.SeedFromRemote(list),不再設EditSource- 移除
using LogicBll.Other.Secret;(不再存在)LogicBll/ItemsBindingManager.cs:
-
DirectSaveToSqliteAndLoad改名SeedFromRemote、加清 dirty/IsNew/增刪修 邏輯、移除冗餘 try/finally-
DataManager.Save()加if (_db == null) return;null guard,避免對未Load/SeedFromRemote的 manager 呼叫SaveAll時 NRELogicBll/LocalDbService.cs+Common/Context/ILocalDbService.cs:UpsertBatchMarkDirty<T>→UpsertBatch<T>(原名誤導;實際上不主動標 dirty,dirty 是由 caller 端先設好)
ViewModel/A.SystemData/PermissionSettingViewModel.cs:
- 移除
using LogicBll.Other.Secret;-
PrgsctBatch型別List<PrgsctBatch>→IEnumerable<PrgsctBatch>(XAMLItemsSourcebinding 不受影響)刪除
LogicBll/Other/Secret/整個資料夾(內只有PrgsctBatch.cs,已搬到 Model)
BatchEditBll.EditSource(抽象屬性)
Breaking Changes
PrgsctBatch型別改隸屬 Model 專案 / namespace 從LogicBll.Other.Secret改為Model:所有 caller 的using需要改。本次掃過的:SecretBll.cs、PermissionSettingViewModel.cs。其他若有依賴需自行調整
BatchEditBll子類別必須提供ItemsBindingManager而非EditSource:目前唯一 subclassSecretBll已調整。任何下游若在自寫 BatchEdit subclass 需重構
DataManager.SeedFromRemote取代DirectSaveToSqliteAndLoad:方法名變更,外部 caller 需更名
UpsertBatchMarkDirty→UpsertBatch:ILocalDbServiceinterface 上的方法名變更
SecretBll.PrgsctBatch不再可寫:原本是{ get; set; }的List<PrgsctBatch>,現在是只讀的IList<PrgsctBatch>(指向DataManager.ObservableCollection)。caller 不能再SecretBll.PrgsctBatch = ...或對它呼叫Add/Remove
Migration Notes
1. 第一次跑這版的 client 端 SQLite:本機資料庫多了一張
PrgsctBatch表- 如果使用
EnsureCreated/ migration:自動處理- 如果手動管理 schema:需建立
PrgsctBatch表,schema 同Prgsct12. caller 的
using升級:所有using LogicBll.Other.Secret;改為using Model;3.
SecretBll.PrgsctBatch用法檢查:以前可能透過此 property 對 listAdd/ 重新賦值,現在這條路被擋掉;若有需求請改透過DataManagerAPI(AddItem/Clear等)4. 若有自寫
BatchEditBllsubclass:- 改實作
ItemsBindingManager抽象屬性(建構子建好後Register<T>())-
SqlQuery把EditSource賦值改為對應DataManager.SeedFromRemote(list)-
WhenSqlQueryTrue等 hook 維持原本 mutation 邏輯即可(mutation 落在dm.ObservableCollection的 instance 上,EF 會自動偵測)對使用者的影響
- 終端使用者無 UI 操作改變
- 權限批次設定(PermissionSetting / SECRET)的「儲存」行為從「diff 我自己算」變「EF 自動判定」;只有真的有變更的 row 才會送上去。對使用者體感應該是一致或更精準
- 第一次跑新版時若 SQLite 沒升級到含
PrgsctBatch表,畫面會在查詢時拋例外;DBA / 升級腳本要先處理本機 schema
-
04-28 scheduler-worker ▸
2026-04-28 — TimerBll 拆出獨立排程 exe(TsERP.SchedulerWorker)
!!! note "後續更新"
本 changelog 描述的 SP
TWORK_SKD_NOTIFYEVENT在 2026-05-04 已被拆成TWORK_SKD_GET_PENDING_NOTIFY與TWORK_SKD_GET_PENDING_AUTOEXEC兩支單一職責 SP,並修正「停用應為是否停用」與「殭屍判定狀態碼還用'X'沒對齊 ADR 0013」兩個積累 bug。詳見 ADR 0015 與 2026-05-04 changelog。摘要
行事曆通知與 AutoPilot 自動化批次的 5 分鐘排程從 WPF App 內的
DispatcherTimer(LogicBll/Timer/TimerBll.cs)拆出來,做成獨立 console exeTsERP.SchedulerWorker,由 Windows 工作排程器每 5 分鐘觸發。從此通知與 AutoPilot 不再依賴「有人開著 ERP」才會跑。詳細決策見 ADR 0009。新增
TsERP.SchedulerWorker/整個專案(net8.0-windows、UseWPF=true、OutputType=Exe)
-
TsERP.SchedulerWorker.csproj-
Program.cs:top-level entry,Global\TsERP_Schedulernamed Mutex 防重疊,exit code0=成功 /1=失敗 /2=被 mutex 擋-
SchedulerJob.cs:取代TimerBll,提供Task<int> RunOnceAsync()-
SchedulerSettings.cs:config POCO-
SchedulerUser.cs:IUserheadless 實作(UserId=SCHED)-
appsettings.json:Scheduler區段(Databases/AlertRecipients/ZombieTimeoutMinutes/BatchSize/DefaultSrvdbid)- 新 SP
TWORK_SKD_UPDATE_EVENT_STATUS:原子化更新事件狀態 + 時間戳
- 行事曆事件「執行中」狀態
'X':已通知/已完成欄位多一個值,配合新執行開始時間欄位做殭屍紀錄回收
變更
LogicBll/Calendar/CalendarBll.cs:新增 3-arg 多載Update已通知(pkid, status, DateTime?)與Update已完成(pkid, status, DateTime?),支援'X'狀態與時間戳。原 2-arg 簽章保留
ViewModel/PageControl/MainWindow/MainWindowBtnTimerViewModel.cs:拿掉TimerBll欄位(VM 殼保留,避免影響 XAML binding)
TsERP.sln:加入TsERP.SchedulerWorker專案
- SP
TWORK_SKD_NOTIFYEVENT行為改變:
- 加
TOP 20、ORDER BY ASC:每次只拿 20 筆- QueryType=2 移除原本 1 小時時間窗
- filter 改為
已完成 NOT IN ('Y','F')+ 殭屍回收('X'超過ZombieTimeoutMinutes視為前一輪 crash 自動拉回)- 失敗事件處理改變:標
'F'後不再自動重跑,需要管理員手動把狀態改回'N'或空
刪除
- 無(
LogicBll/Timer/TimerBll.cs保留為 fallback,未來確認穩定後再評估移除)
Breaking Changes
- DB schema 變更:
twork_skd_eventitem(base table)需新增執行開始時間 datetime NULL欄位
- SP
TWORK_SKD_NOTIFYEVENT行為改變:見上方「變更」段;如有外部程式直接呼叫此 SP 並依賴原 1 小時時間窗或全部撈出的行為,需重新驗證
- 新增 SP
TWORK_SKD_UPDATE_EVENT_STATUS:CalendarBll 新多載呼叫;DB 端必須先建立此 SP 才能升級 ERP / SchedulerWorker
- 失敗事件不再自動重跑:標
'F'後跳過,需要管理員手動處理;先前依賴「自動重試直到成功」的事件流程需要調整 SOP
Migration Notes
1. DBA 端:對每個 darb DB 執行
-
ALTER TABLE twork_skd_eventitem ADD 執行開始時間 datetime NULL-
ALTER PROCEDURE TWORK_SKD_NOTIFYEVENT(加TOP 20、ORDER BY ASC、移除 1 小時時間窗、改 filter 為已完成 NOT IN ('Y','F')+ 殭屍回收)-
CREATE PROCEDURE TWORK_SKD_UPDATE_EVENT_STATUS2. Build & publish
TsERP.SchedulerWorker到 server(建議放D:\TsERP\Scheduler\,self-contained 發布)3. 建專屬服務帳號(例如
TsERP\schedsvc),加入本機 Administrators 群組,密碼設「永不過期」4. 建立 Windows 工作排程:
- 觸發:每 5 分鐘
- 動作:執行
TsERP.SchedulerWorker.exe- 設定:「不要啟動新執行個體」(與 named mutex 雙保險)
- 帳號:上一步建立的服務帳號,勾「不論使用者是否登入皆執行」
5. (可選)監控建議:
- Mailgun dashboard 看寄信統計變化
- SQL Server agent log 看 SP 觸發次數
- 工作排程器歷程記錄看 exit code(
0=成功 /1=失敗 /2=被 mutex 擋)- 定期巡檢
已完成 = 'F'的事件,避免長期堆積對使用者的影響
- 終端使用者無 UI 操作改變
- 行事曆事件通知與 AutoPilot 觸發改由 server 端排程,不再受「有人開著 ERP」影響;下班與週末時段通知也會照常發送
- 若事件第一次失敗(
'F'),不會再自動重試,需聯繫系統管理員處理
-
04-24 mold ▸
2026-04-24 — D.Bom 新增「模具資料表維護」模組
摘要
D.Bom 工程資料模組新增「模具資料表維護」頁面,提供模具主檔(
mold)、品號明細(moldmno)、管制紀錄(moldctrl)三張表的查詢/新增/編輯/刪除,加上「償卻重算」與「Excel 匯出」兩個專屬動作。償卻金額計算統一走 SPDARB_QRY_MOLD,避免畫面、報表、匯出三處邏輯漂移。詳細決策見 ADR 0008。新增
- D.Bom → 模具資料表維護:完整 CRUD + 償卻重算 + Excel 匯出
- 主檔含模具編號、圖號、開模廠商、模具費用、基準/償卻台數、起始分攤日等 67 欄
- 品號明細維護該模具對應的客戶品號與內部品號
- 管制紀錄維護模具維修/送修/報廢等事件時序
-
重算償卻:統一呼叫DARB_QRY_MOLD更新模具分攤費-
Excel 匯出:逐筆呼叫DARB_QRY_MOLD匯出全部模具(非僅當前查詢結果)- 手冊頁:
docs/D-Bom/mold.md
變更
TsERP/Lang/TableMap.json:修正"moldctrl"原本錯誤對應到自己的問題,改為對應"twork_bom_vw_moldctrl";新增"mold"→"twork_bom_vw_mold"、"moldmno"→"twork_bom_vw_moldmno"映射
TsERP/Lang/ClassAliasMap.json:新增Twork_bom_vw_moldctrl{Model,Source}完整別名;移除舊的Moldctrl{Model,Source}短名別名(原短名會跟未來可能加入的moldctrl實體表衝突)
刪除
ClassAliasMap.json中舊的MoldctrlModel/MoldctrlSource短名別名
Breaking Changes
- 外部整合若依賴
moldctrl短名別名:透過ClassAliasMap反射取型別的程式碼(例如自訂報表、整合腳本),若用MoldctrlModel/MoldctrlSource會取不到,需改成完整名Twork_bom_vw_moldctrlModel/Twork_bom_vw_moldctrlSource
Migration Notes
- 升級後如使用外部整合依賴
moldctrl表名別名,請改用完整twork_bom_vw_moldctrl
- DB 端需確認
DARB_QRY_MOLD、DARB_GETDATA4SP 皆存在於目標環境(多數既有環境已部署)
- 首次開啟頁面若顯示權限不足,請檢查使用者是否具備
D-Bom-Mold相關動作權限
- 使用 Excel 匯出前建議先確認模具筆數;超過 5000 筆時匯出時間會隨筆數線性增加
-
04-24 bom-flow-diagram-tab ▸
2026-04-24 — Bom_flow 新增「流程圖(圖形)」tab
摘要
D.Bom\Bom_flow多一個 tab「流程圖圖形」,用 Telerik RadDiagram 把 BOM 樹渲染成節點 + 連線圖,提供放大縮小、垂直/水平切換、自動縮放等互動。新增
- 第 4 個 tab「流程圖圖形」(
DisplayMeta[流程圖圖形]),位於 Bom_flow 右側 TabControl 的最末
- Toolbar 5 顆按鈕:放大、縮小、自動縮放、垂直排列、水平排列、重新排版
- 節點形狀自動切換:
- 根節點(查詢組件):深色圓角矩形
- 物料節點:白底矩形
- 製程節點:淺黃底橢圓
- 每個節點顯示 4 行:品號/部門工程 / 品名 / 數量單位或工時 / 層數+上階+序號
變更
- 無
刪除
- 無(原 90 欄「流程圖」tab 完全保留)
Breaking Changes
- 無
Migration Notes
- 終端使用者無操作改變;開新 tab 自動出現
- 開發者:若要新增 DisplayMeta key,兩份 JSON 都要更新:
-
Common\Lang\Xaml\Bom_flow.json(Debug fallback 來源)-
TsERP\Lang\Xaml1\Xaml.json(Release 來源) - 第 4 個 tab「流程圖圖形」(
-
04-22 velopack-updating-window ▸
2026-04-22 — Velopack 自動更新加上「正在更新」提示視窗
摘要
Velopack 自動更新流程原本完全無聲:啟動後背景檢查、背景下載、下載完直接
ApplyUpdatesAndRestart關閉重開,使用者體感像是登入畫面正在操作時突然被踢掉。現在在發現更新後顯示一個小型 Topmost 視窗,告知使用者「正在下載更新」與進度百分比,下載完成後顯示「即將重新啟動」再執行ApplyUpdatesAndRestart。新增
TsERP/UpdatingWindow.xaml/.xaml.cs:380×130 無邊框置中視窗
-
Topmost="True"、ShowInTaskbar="False"、ResizeMode="NoResize"、無 caption- 一行狀態文字 + 不定/定量進度條
- 公開方法:
SetStatus(string)/SetProgress(int)(呼叫SetProgress時進度條切為定量模式)變更
TsERP/App.xaml.csCheckForUpdatesAsync:偵測到更新時顯示UpdatingWindow
- 發現更新 → 狀態文字:「發現新版本,正在下載更新...」
- 下載進度回呼 → 「正在下載更新(N%)...」+ 進度條填色
- 下載完成 → 「更新下載完成,即將重新啟動...」
-
ApplyUpdatesAndRestart(update)關閉 App,視窗隨程序結束消失- 例外路徑會 Dispatcher 關閉視窗,避免 UI 殘留
- 改用
DownloadUpdatesAsync(update, progress)重載(Velopack 0.0.1298 支援)刪除
- 無
Breaking Changes
- 無(行為變化屬於 UX 提示,不影響更新結果或版本流程)
Migration Notes
- 使用者端無需動作,下次自動更新即生效
- 已知限制:
ApplyUpdatesAndRestart觸發的 VelopackUpdate.exe在切換過程仍可能短暫閃現其自身 UI,本次調整不處理該閃窗。如需完全無縫,需改走WaitExitThenApplyUpdates並搭配外部重啟腳本,成本較高,暫不納入
- 若 debugger 附加中(
Debugger.IsAttached),維持原本略過更新流程的行為,開發環境不會看到此視窗
-
04-22 aiquery-table-whitelist ▸
2026-04-22 — AIQuery 加上表名白名單驗證
摘要
AIQuery 的
TWORK_AI_QUERYSP 在執行前多做一層「FROM/JOIN 後的表名必須登記在_ai_schema」的驗證,同時強化 Claude system prompt,禁止使用sys.*、INFORMATION_SCHEMA.*、以底線開頭的系統表與 CTE。目的在防止 AI 幻覺或 prompt injection 寫出白名單外的查詢。詳細決策見 ADR 0007。新增
SqlBI/TWORK_AI_QUERY.sql:新增步驟 3.5「白名單檢查」
- 掃描 SQL 中
FROM與JOIN後的表名 token- 去掉
[]與schema.前綴後,逐一與_ai_schema.sql_name比對- 任一不符即
RAISERROR並寫入_ai_query_log,錯誤訊息:「查詢包含不在白名單 (_ai_schema) 的資料表,已被拒絕」變更
LogicBll/AI/AIQueryBll.csBuildSystemPrompt:system prompt 新增三條規則
- 嚴禁查詢
sys.*、INFORMATION_SCHEMA.*、任何以底線開頭的系統表- 超出白名單的查詢請求應拒絕並說明,不得自行臆測表名
- 禁止使用 CTE (
WITH ... AS),需用子查詢或 JOIN 改寫刪除
- 無
Breaking Changes
- 使用 CTE 的舊查詢會被擋下:若既有流程或 AI 回答慣例會使用
WITH ... AS,現在會被 SP 回「不在白名單」錯誤。已透過 prompt 指引 Claude 改用子查詢/JOIN
- 字串常數含特定模式可能誤殺:
WHERE 備註 LIKE '%FROM XX%'之類、字串內剛好包含FROM的查詢,token 抽取會誤判。若遇到可調整問法
Migration Notes
- DB 端要執行
SqlBI/TWORK_AI_QUERY.sql(CREATE OR ALTER PROCEDURE)套用新版 SP
_ai_schema表的維運更關鍵:要讓 AI 能查新表,必須先在_ai_schema登記,否則即使有 SELECT 權限也會被 SP 擋下
- 使用者若反映「AI 以前能回答、現在說被拒絕」,先查
_ai_query_log.執行結果:
- 「查詢包含不在白名單」→ 表名未登記 → 補
_ai_schema- 「偵測到不允許的 SQL 語法」→ 關鍵字黑名單擋(維持既有行為)
-
04-20 velopack-upload-incremental ▸
2026-04-20 — Velopack 上傳改為逐檔增量
摘要
release-velopack.ps1的上傳邏輯從az storage blob upload-batch --overwrite(每次全覆蓋)改為先查遠端清單、逐檔決定 NEW / OVER / SKIP。每次發布頻寬從 ~970MB 降至 ~360MB(約 60% 節省)。同時修掉 PS 5.1 +$ErrorActionPreference='Stop'對 az CLI stderr 警告過敏的問題。詳細決策見 ADR 0005。新增
- 無
變更
TsERP/release-velopack.ps1上傳區塊:
- 上傳前先
az storage blob list取得遠端 blob + size map- 逐檔決策:遠端沒有 → upload;遠端有 + 是 nupkg → skip;遠端有 + size 相同 → skip;其他 → overwrite
- az 呼叫包在
$ErrorActionPreference='Continue'的 try/finally,避免 az CLI 寫 stderr 的 info/warning 被 PS 5.1 升級為NativeCommandError終止 script- 個別
az storage blob upload加--only-show-errors抑制雜訊- Console 輸出格式:每檔一行
NEW/OVER/SKIP <name> (<size>),最末印統計NEW x / OVER y / SKIP z刪除
upload-batch --overwrite單次呼叫(被逐檔 upload 取代)
- 中途嘗試過的
az storage blob sync版本(依賴 AzCopy、使用者機器 DNS 被擋 → 失敗退回)
Breaking Changes
- 無(遠端 container 內容與 upload-batch --overwrite 結果等價)
Migration Notes
- 使用者端無需動作
- 發布端改動已在
release-velopack.ps1,下次執行自動生效
- 若未來 Velopack 在
Releases/新增其他「每版都改內容、檔名固定」的檔案類型,現有 size 比對邏輯自動處理;若新增「每版檔名改、內容固定」的類型則視同 new file 上傳、行為正確
- 若未來容器 blob 數量成長到 10000+,
az storage blob list需評估分頁(目前單次呼叫 return 完整清單)
-
04-20 velopack-sign-exe-only ▸
2026-04-20 — Velopack 簽章只簽 EXE
摘要
release-velopack.ps1的vpk pack加上--signExclude '.*\.dll',發布時只簽主程式 EXE,不再簽所有內部 DLL。發布簽章時間從 ~7 分鐘縮短為數秒,USB token PIN 彈窗從 60+ 次降至 2–3 次。詳細決策見 ADR 0004。新增
- 無
變更
TsERP/release-velopack.ps1:vpk pack參數新增--signExclude '.*\.dll'
- 僅在
-SkipSign未指定時生效- 實際被簽的檔案:
TsERP.exe+ Velopack 產出的Setup.exe/Update.exe- 本專案各 DLL(
Common.dll/ViewModel.dll/LogicBll.dll/Model.dll/TsControl.dll/AutoPilot.dll/ 各領域模組)不再簽章刪除
- 無
Breaking Changes
- 無(主 EXE 仍簽章,SmartScreen 發行者顯示不變)
Migration Notes
- 使用者端無需動作,下次 Velopack 自動更新即套用
- 若未來遇到 AppLocker / WDAC 嚴格環境擋未簽 DLL,走 publisher allowlist 或 hash allowlist 處理;尚無此類客訴
- 若未來要改為全面簽章但保留速度,評估遷移至 Azure Trusted Signing(見 ADR 0004 Alternatives Considered)
-
04-20 velopack-release-pipeline ▸
2026-04-20 — Velopack 發布管線
摘要
新增 Velopack 一鍵發布腳本,整合版號遞增、publish、簽章、Azure 上傳、git commit。詳細決策見 ADR 0003。
新增
TsERP/release-velopack.ps1:Velopack 發布主腳本
- 自動把 csproj 的
<VelopackVersion>patch 段 +1-
dotnet publish->vpk download->vpk pack(USB 硬體簽章)->az storage blob upload-batch->git commit- 四個逃生開關:
-SkipDownload(首次發布)、-SkipSign(USB 未插)、-SkipUpload(本地驗證)、-SkipCommitTsERP.csproj新增<VelopackVersion>1.0.1</VelopackVersion>標籤
變更
- 無(既有 ClickOnce 腳本
autodeploy.ps1/darbtestDeploy.ps1完全不動)
刪除
MainWindow.xaml.cs的CheckForUpdate()方法與其在Window_Loaded的呼叫
- 原本會彈「發現新版本 / 是否立即重啟套用」MessageBox
- 與
App.xaml.cs:63Startup 事件裡的靜默自動更新並行執行,兩個UpdateManager同時寫入%LocalAppData%\YanYue.ERP\packages\會產生 file lock 錯誤- 保留
App.xaml.cs的靜默版(啟動時檢查 → 下載 →ApplyUpdatesAndRestart),使用者完全不介入- MainWindow.xaml.cs 不再 using
Velopack/Velopack.Sources,MessageBoxalias 也移除(除了 CheckForUpdate 沒別處用)
Breaking Changes
- 無(Velopack 更新通道
darb-vpk目前僅有App.xaml.cs讀取,使用者端安裝的 ClickOnce 版不受影響)
Migration Notes
首次發布步驟
darb-vpkcontainer 若尚無RELEASES檔,第一次跑要加-SkipDownload:cd C:\ERPV2\TsERP .\release-velopack.ps1 -SkipDownload之後每次發布:
.\release-velopack.ps1前置需求
1. 已安裝
dotnetSDK、vpk全域工具(dotnet tool install -g vpk)、azCLI、git2. USB 硬體簽章 token 已插上(憑證指紋
66AE9BC65D5516A65B8F4860F4F815F56F5CFBFB)3. PowerShell 執行原則若限制未簽章腳本,需用
Set-AuthenticodeSignature幫release-velopack.ps1簽一次(同autodeploy.ps1的做法)手動介入情境
- pack 或 upload 失敗、csproj 版號已寫回但還沒 commit:直接重跑,失敗前的新版號會繼續被用
- git commit 之後才發現 upload 有問題:
git reset --soft HEAD~1退回 commit,處理完再重推
版本號升級
- 預設 patch +1:
1.0.1→1.0.2→1.0.3…
- Major / Minor bump 需要時直接編 csproj 的
<VelopackVersion>(腳本下次執行會從該值繼續 +1)
-
04-17 operator-alignment ▸
2026-04-17 — 查詢條件運算子對齊 Telerik
摘要
全系統查詢畫面的「運算子」下拉重新整理,文字、行為與排序全部對齊 Telerik RadGridView 內建 filter。詳細設計決策見 ADR 0001,使用者操作說明見 查詢條件運算子。
新增
- 真正的「等於」(代碼 A):產生
col = 'x'
- 真正的「不等於」(代碼 B):產生
col <> 'x'
- 結尾為 / EndsWith(代碼 H):產生
col LIKE '%x'
- 被包含 / IsContainedIn(代碼 K):產生
col IN (x,y,z),值用逗號分隔
- 不被包含 / IsNotContainedIn(代碼 L):產生
col NOT IN (...)
- 不等於Null / IsNotNull(代碼 N):產生
col IS NOT NULL
- 不等於空白 / IsNotEmpty(代碼 P)
變更
- 下拉排序:改為 Telerik 自然順序(等於 → 不等於 → 大於 → … → StartsWith → EndsWith → Contains → …)
- 預設運算子:維持重構前的行為,使用 不等於 / IsNotEqualTo(新代碼 B,對應舊 index [2] 的 Not Equal(<>))
- 中文文字修正:
- 舊「等於(=)」實際是 StartsWith → 改稱「開始於」
- 舊「不等於(<>)」實際是 DoesNotStartWith → 整合為「不包含」
- 其他 label 全面對齊 Telerik 繁中 UI 用字
刪除
- 開頭不為 / DoesNotStartWith(舊 C):無 Telerik 對等,遷移到「不包含」
- 多關鍵字包含 / Multi-Keyword Contains(舊 J):無 Telerik 對等,遷移到「包含」。若需多關鍵字查詢,請改用多個「包含」條件以 OR 串接
Breaking Changes
Twork_sys_vw_zcond_define.SymbolNo所有代碼的意義都變了。部署前必須跑 migration SQL(見 ADR 0001 末尾),否則所有客戶既有自訂查詢條件會靜默變意義
- FilterWindow 下拉原本硬限制只顯示代碼
<= "H",現改為白名單顯示 A–Q(排除自家延伸 R/S)
BrowseInitialzation.GetConditionSourceByNoSqlDefaultValue取預設值不再用SymbolNoList[2],改為按代碼"B"(不等於)明確查找
Migration
1. 備份
Twork_sys_vw_zcond_define2. 於測試 DB 執行 ADR 0001 提供的
UPDATE ... CASE WHEN腳本3. 驗證筆數與抽樣結果
4. 正式機執行
5. 若有客戶自訂條件使用到舊 C 或舊 J,執行完 migration 後會被遷移到語意接近但不完全相同的 operator,建議通知使用者重新確認
影響檔案
- 核心 4 個:
LCLZcondOperator.cs、Xaml.json、BrowseConditionHelper.cs、SQLiteConditionHelper.cs
- 連帶 7 個:
BindingListCollectionViewFilterHelper.cs、BrowseInitialzation.cs、FilterWindowViewModel.cs、FilterMethod.cs、BrowseKeyWord.cs、ConditionPoco.cs、加上 9 個業務 ViewModel 的硬編碼遷移
後續修正(同日 hotfix)
- 預設運算子回正:重構初版誤改成
"G"(StartsWith),還原為"B"(不等於)以維持歷史行為
Common/Lang/Xaml/Shared.json補 O–S:DEBUG 模式優先讀Lang/Xaml/分檔,初版漏改此檔導致下拉 O 以後的新運算子顯示不出說明文字。Release 模式讀Lang/Xaml1/Xaml.json不受影響
- 真正的「等於」(代碼 A):產生