修改紀錄
每次新增功能、變更、刪除、Breaking Changes 的時間軸。共 66 篇。
📅 2026 年 6 月 (16 篇)
-
06-04 sliy-excel-import-bulk-perf ▸
2026-06-04 總帳傳票 Excel 匯入:大批效能、驗證與崩潰修正
摘要
總帳傳票(SLIY)的「Excel 匯入」在匯入大量分錄時又慢又會崩潰、且不合法資料照匯。本次將大批匯入降到秒級、修正崩潰,並在匯入前整批驗證、不合法即中止。
變更
- 匯入大幅加速:6,000+筆從數十秒/分鐘級降到秒級。原本每多 500 列耗時不降反升(O(N²)),現已消除。
- 不合法資料不再被匯入:匯入前先驗證所有列的科目代號(須存在且「可輸入傳票」)。只要有任一列不合法,整批中止,並以單一視窗列出所有問題列(標示 Excel 實際列號與原因),不再逐列跳出大量訊息框。
- 讀檔失敗有明確提示:Excel 檔損毀/被開啟/格式不符時,顯示明確訊息而非讓程式崩潰。
修正
- 修正大批匯入時偶發的程式崩潰(
ExecutionEngineException)。
刪除
- 無(移除的是開發期間的暫時性效能量測碼,對使用者無影響)。
Breaking Changes
- 無。匯入後的金額、匯率、借貸合計與修正前一致。
Migration Notes
- 無需資料或設定遷移,更新版本後即生效。
詳見 ADR 0028。
-
06-03 sliy-forbidden-voucher-early-prompt ▸
總帳傳票(sliy)「禁止輸入傳票」改為科目格子當下攔截、焦點留在格內
摘要
總帳傳票(
Sliy,程式代號 SLIY,另含 Budget、Sliyyear)輸入會計科目時,若該科目「輸入傳票」非 Y(統制/標題科目),原本只在整列提交(RowValidating)時才擋;現改為在離開科目代號格子前(CellValidating)就攔截,提示「本科目禁止輸入傳票」並把焦點留在科目格子內,游標不會跳到下一格,逼使用者當下改正。變更
InjectAcclValidationRuleBehavior:除原本的RowValidating外,新增訂閱CellValidating。只針對「科目代號」欄(依DataMemberBinding路徑判斷),輸入禁止入帳的科目時設e.IsValid = false(取消提交、焦點留格內)+e.ErrorMessage,並顯示訊息。
AcclValidation.CanInputVoucher(glAccountCode, out msg):新增的輕量檢查方法,只驗「輸入傳票」必須為 Y;查無科目時不在此擋(交給 RowValidating 完整檢查)。
- 「禁止輸入傳票」的攔截集中在 CellValidating,不放在
OnDetailGLCodeChanged(該處於格子提交後才執行、留不住焦點);常用摘要自動帶入仍維持在OnDetailGLCodeChanged。
新增 / 刪除 / Breaking Changes
無資料欄位異動;無 Breaking Changes。
Migration Notes
- 整列提交時的
RowValidating完整驗證(科目存在性、控制方式 1~9、客廠別等)維持不變,仍為存檔前最後防線。
- 其他需整列欄位齊全才能判斷的控制驗證(索引/部門/客廠等)仍留在 RowValidating 階段,未改於離開單一格子時觸發。
- 程式化批次填入(如 Excel 匯入)不經過 UI 編輯、不觸發 CellValidating,禁止科目仍由 RowValidating/存檔驗證攔下。
-
06-03 accl-default-memo ▸
會計科目(accl)新增「常用摘要」欄位
摘要
會計科目主檔(
Accl,程式代號 ACCL)新增「常用摘要」輸入欄位。資料模型、DB view 與總帳傳票(sliy)帶入邏輯原本就支援此欄位,僅缺主檔的輸入介面,本次補上。新增
Accl.xaml:在「英文名稱」與「大分類」之間(原 Row 3 空列)新增「常用摘要」標籤 + 文字輸入框,綁定常用摘要屬性(Twork_acc_vw_acclSource,長度 80)。
- 多語系字串
常用摘要(DefaultMemo):加入Common/Lang/Xaml/Accl.json與TsERP/Lang/Xaml1/Xaml.json。
變更
無。
刪除
無。
Breaking Changes
無。
Migration Notes
- 無需資料庫異動:
常用摘要欄位於Twork_acc_vw_acclview 既已存在。
- 行為延續:在會計科目設定「常用摘要」後,於總帳傳票(sliy)輸入該科目且該列摘要為空時,會自動帶入此摘要(既有邏輯,見
SliyViewModel.OnDetailGLCodeChanged)。
-
06-02 sliy-save-checks-and-excel-import ▸
2026-06-02 總帳傳票(Sliy):存檔檢查補強 + Excel 匯入分錄
摘要
依 VFP 舊版 sliy 補齊三項:原幣借貸不平衡會跳提示、存檔時擋無效/空白傳票日期、新增「Excel 匯入」分錄按鈕。
新增
- 傳票編輯區新增「Excel 匯入」按鈕:可從 Excel 匯入分錄(第 1 列為標題,第 2 列起 A 欄=科目代號、C 欄=原借方金額、D 欄=原貸方金額),匯入時自動帶本位幣、匯率 1 並換算本幣金額。對應舊版 excel轉入鈕。
變更
- 存檔時若原幣借貸不平衡,會跳提示(僅提示、不阻擋存檔;本幣借貸不平衡仍阻擋)。先前此提示不會出現。
- 存檔時若傳票日期空白或非有效日期,會阻擋並提示(對應舊版檢查)。
- 科目代號驗證新增「本科目禁止輸入傳票」檢查:科目的「輸入傳票」非 Y(統制/標題科目)時,該分錄列驗證不通過、無法存檔(對應舊版 sql_acclno1.Valid,先前漏移植)。新增訊息
ThisGLAccountCannotInputVoucher。
- 科目控制方式驗證(分類索引/部門/客廠/項目/員工/專案/產品群組必填)改為「新增(A)與修改(E)的列」都驗證;先前只驗新增列,編輯既有列不重驗。
- 輸入科目代號時的自動補平金額改為「本幣設精確、原幣=本幣÷匯率」(對應舊版);先前以原幣四捨五入回算本幣,外幣情況可能造成本幣借貸差 1 而擋存。
- 輸入科目代號時,若科目有設「常用摘要」且該列摘要為空,自動帶入常用摘要(對應舊版,先前未做)。
- 明細的「金額」改為隨數量/單價/折扣自動計算:金額 = 四捨五入(數量 × 單價 × 折扣, 幣別小數位);新增明細的折扣預設為 1(對應舊版,先前完全沒做此計算、折扣預設 0 會算出 0)。
Breaking Changes
無。
Migration Notes
無需操作;更新版本後即生效。Excel 匯入的欄位格式:A=科目代號、C=原借方金額、D=原貸方金額,資料自第 2 列開始。
-
06-02 radgridview-horizontal-scroll-fix ▸
2026-06-02 修正 RadGridView 水平捲軸間歇拖不動
摘要
修正資料表(RadGridView)水平捲軸「有時拖得動、有時拖不動」的問題,常見於傳票 Sliy 等欄位很多的明細頁。修正後水平捲軸可穩定拖曳、可捲到最右側欄位。
變更
- 全系統資料表關閉「欄虛擬化」(
EnableColumnVirtualization=False),改在共用樣式統一設定。原因是寬表的 off-screen 欄未被量測會讓水平捲動範圍算錯,導致捲軸拖不動。詳見 ADR 0026。
Breaking Changes
無。
Migration Notes
無需操作;更新版本後即生效。
- 全系統資料表關閉「欄虛擬化」(
-
06-02 ftrlima4-fsheetimport-edge-fixes ▸
2026-06-02 零用金支出/財傳單快速輸入:邊角補齊
摘要
依 VFP 舊版補齊零用金支出(Ftrlima_4)與財傳單快速輸入(FsheetImport)幾項細節。
新增
- 零用金支出:新增「全選 / 全部不選」按鈕,一鍵全勾/全不勾並即時更新金額彙總(對應舊版全選鈕/不選鈕)。
變更
- 財傳單快速輸入:載入既有分錄時,略過沒有科目代號或已標記刪除的列(對應舊版只載入有效分錄)。
- 財傳單快速輸入:新增空白列時,摘要自動帶入上一列摘要,省去重複輸入(對應舊版摘要自動帶值)。
- 財傳單快速輸入:查詢匯率時,外幣有帳戶的列改為「貸方(付款)用銀行日報表帳戶匯率、借方(收款)用掛牌買價」,與舊版一致(先前一律用銀行帳戶匯率)。
已知未調整
- 財傳單快速輸入查詢匯率的「掛牌買價取前一營業日(T-1)」日期慣例尚未調整,待確認新版匯率 SP 與「付款匯率日」是否已內含 T-1,避免重複位移。
Breaking Changes
無。
Migration Notes
無需操作;更新版本後即生效。
-
06-02 ftrlima4-amount-summary ▸
2026-06-02 零用金支出:補本幣/原幣金額彙總顯示
摘要
「零用金支出」(Ftrlima_4) 視窗補上舊版有、新版漏掉的「原幣金額彙總/本幣金額彙總」即時顯示。勾選/取消勾選主檔列時,下方會即時顯示目前已勾選單據其明細的原幣含稅、本幣含稅合計。
新增
- 視窗底部左側顯示「原幣金額彙總」「本幣金額彙總」兩個唯讀金額欄。
- 數值 = 所有已勾選主檔列其明細的
原幣含稅/本幣含稅加總,隨勾選即時更新。
Breaking Changes
無。
Migration Notes
無需操作;更新版本後即生效。
-
06-02 fsheetimport-copy-group-and-currency-balance ▸
2026-06-02 財傳單快速輸入:複製分組修正 + 原幣不平衡提示
摘要
「財傳單快速輸入」(FsheetImport) 的「複製」會把複製出來的分錄列沿用原本的組數,導致之後「查詢匯率」計算各組匯兌損益時把原組與複製組混在一起算錯。已修正為複製時掛新組數。另補上原幣借貸不平衡的提示(對應舊版行為)。
變更
- 複製:複製出來的分錄列改掛新的組數(
Detail.Max(組數)+1),成為獨立分組,匯兌損益依組正確計算。
- 確認/存回時:原幣借貸不平衡會跳提示(僅提示、不阻擋存檔);本幣借貸不平衡維持原本的阻擋。
影響
- 複製分錄後再查詢匯率,匯兌損益分組計算正確。
- 原幣借貸不一致時會多一個提示視窗(不影響可否存檔)。
Breaking Changes
無。
Migration Notes
無需操作;更新版本後即生效。
- 複製:複製出來的分錄列改掛新的組數(
-
06-02 fsheet1-bill-wire-fx-recompute ▸
2026-06-02 批次改財傳單日期:補票據/電匯外幣匯率重算
摘要
「批次改財傳單日期」(fsheet_1) 在變更付款日期時,原本只重算沖帳分錄的匯率,票據與電匯的外幣匯率沒有跟著新日期更新(電匯本幣金額也未重算)。對照 VFP 舊版
修正匯率補齊,使三類資料一致。變更
Fsheet_1Bll.ChangeRate()新增票據(Detail1)、電匯(Detail2) 兩段重算:幣別=本位幣→匯率 1;金額>0 取匯率表(使用匯率),否則取帳戶日報表匯率。電匯另以CalculateAmount()重算本幣金額(=金額×匯率,依幣別小數位)。
影響
- 含外幣票據/電匯的財傳單,批次改日期後其匯率與電匯本幣金額會更新為新付款日期對應的匯率(先前維持查詢當下的舊值)。本位幣資料不受影響。
Breaking Changes
無。
Migration Notes
無需操作;更新版本後即生效。
-
06-02 ai-agent-mcp ▸
2026-06-02 — 全新 MCP-first 內嵌 AI 智慧助理
摘要
主視窗 AI 按鈕改開啟全新的「AI 智慧助理」對話視窗:使用者用自然語言查詢 ERP 唯讀資料、或要求分析出圖(Vega-Lite,WebView2 渲染)。底層採 MCP-first 架構,工具由本機 stdio MCP server 暴露,同一支 server 也能掛進 Claude Desktop / Claude Code。資料存取走 DB 既有的
TWORK_AI_QUERY閘道(LLM 寫 SELECT → proc 驗證/白名單/限筆/稽核後執行)。同時清理數支舊 AI 試作(含一支外洩 OpenAI key 的檔案)。新增
- 新專案:
-
TsErp.Ai.Core:ClaudeClient(串流 + 工具迴圈 + prompt caching)、IAiTool/ErpToolContext、McpClientHost、VegaSpecBuilder、McpServerLocator、JsonDefaults(中文不 escape)。-
TsErp.Ai.Tools:4 支工具list_views/describe_view/run_sql/run_analysis;AiQueryExecutor(呼叫dbo.TWORK_AI_QUERY)、CatalogSqlReader(catalog 直查_ai_schema/INFORMATION_SCHEMA)、ViewCatalogLoader、ErpAiToolRegistry。-
TsErp.Ai.Mcp.Server:MCP-over-stdio(JSON-RPC 2.0)server exe。- WPF:
ViewModel/AIQuery/ChatAgentViewModel.cs+TsERP/AIQuery/ChatAgentView.xaml(.cs)(TelerikRadChat+ WebView2 圖表區);App.xaml加ChatAgentViewModelDataTemplate。
- 可查資料:以 DB 的
_ai_schema為準(執行期載入;實測 darbnogicsa1 有 272 個可查 view),唯讀。加可查表 = 改_ai_schema,不需改程式。
.mcp.json加tserp條目,供 Claude Code / Desktop 掛載同一支 server(Windows 驗證免帳密;env 帶TSERP_AI_RO_CONN/TSERP_AI_DB)。
資料存取(LLM 寫 SQL → TWORK_AI_QUERY)
run_sql/run_analysis接收 LLM 寫的 SELECT,經dbo.TWORK_AI_QUERY執行:該 proc 強制只准 SELECT、擋寫入/危險關鍵字、FROM/JOIN表須在_ai_schema白名單、自動TOP限筆、寫_ai_query_log稽核。
- 初版的「結構化參數 + 程式直查 + 程式白名單」(
QueryDataTool/WhitelistSql/PlanRunner/ReadOnlySqlExecutor) 已移除,改由 DB 閘道把關(見 ADR 0027 演進紀錄)。
變更
- 主視窗 AI 按鈕(
MainWindowBtnAIViewModel.Execute)→ 開啟新ChatAgentViewModel(ShowAiWindow())。
WindowService.ShowAiWindow():由舊AIQueryViewModel改為ChatAgentViewModel。
FoundryPlanService:移除把 AI 輸出寫死到C:\temps\tt.json的 debug 寫檔(此服務仍由Qc_qry3使用,未刪)。
刪除
Common/AI/ChatGptHelper.cs(含明文 OpenAI service-account key,僅一處註解引用,已刪除;該金鑰應於 OpenAI 後台撤銷)。
- 舊 AIQuery 視窗:
ViewModel/AIQuery/AIQueryViewModel.cs、TsERP/AIQuery/AIQueryView.xaml(.cs)、LogicBll/AI/AIQueryBll.cs、App.xaml對應 DataTemplate。
- 語音 AI:
ViewModel/MainWindowPage/AIPageViewModel.cs、TsERP/AIRightSide.xaml(.cs)、Common/TsAzure/SpeechHelper.cs,及其 DI 註冊與 MainWindow VM 注入。
設定需求(系統參數表 `_prgpar`)
CLAUDE_API_KEY(必填):Anthropic Claude API 金鑰。
CLAUDE_MODEL(選填):預設claude-sonnet-4-6。
AI_MCP_SERVER_PATH(選填):MCP server exe 路徑,預設自動尋找。
Breaking Changes
- 舊「AI 資料查詢助理」(AIQuery,自行產 SQL 執行)已移除,由新「AI 智慧助理」取代;查詢改走白名單工具,行為與可查範圍不同。
- 語音 AI 頁面移除。
Migration Notes
- 建置
TsErp.Ai.Mcp.Server(已設為 TsERP 建置相依,會自動先建)。WPF 端由McpServerLocator自動定位 exe;部署時請確保 server 輸出檔與主程式同目錄或以AI_MCP_SERVER_PATH指定。
- 唯讀保證目前以
ApplicationIntent=ReadOnly暫代,建議後續改用專用唯讀 SQL 帳號(DB 層防寫)。
- 安全:請至 OpenAI 後台撤銷舊
ChatGptHelper內的 service-account key。
相關
- [[ADR 0027 — MCP-first 內嵌 AI Agent]](../decisions/0027-mcp-first-ai-agent.md)
-
06-01 usp-新增程式-stored-proc ▸
2026-06-01 新增 `usp_新增程式` 預存程序
摘要
新增一支 SQL Server 預存程序
dbo.usp_新增程式,用來「一次登錄一支新程式」,自動寫入下列 5 張系統表,省去逐表手動 INSERT。已部署於 darbnogicsa1。
程式碼版控於
sql/usp_新增程式.sql。新增
sql/usp_新增程式.sql— 預存程序原始碼。
- 一次寫入:
| 表 | 列數 | pkid |
|----|------|------|
|
_menum(程式主檔)| 1 | 手動 MAX+1 ||
_menud(選單明細/所屬模組)| 1 | 手動 MAX+1 ||
_sheetno(單號設定)| 1 | identity 自動 ||
_tableseq(資料表序號)| 1(該表不存在才補)| 手動 MAX+1 ||
prgsct1(程式權限)| 每使用者 1 列 | 手動 MAX+1 連號 |行為重點
- 跨表關聯鍵:
_menum.類別名稱/_sheetno.單據別/prgsct1.類別名稱= 英文
ProgramClass;
_menum.程式名稱/_menud.程式名稱= 中文顯示名稱。- prgsct1 每使用者一筆:對既有
prgsct1的DISTINCT linkid(= 全部使用者)
逐一產生一列,
linkid即使用者鍵。- 6 個主要權限(瀏覽/明細/修改/新增/刪除/列印)由參數帶入。
- 其餘權限欄位(其他 1–36、看單價、xls、pivot、mail、流程、附件、複製、審核、
自動審核等)一律重設為 0/空白,不繼承任何範本。
- 不讀取
secret1帳密表。- 共通系統欄位:
srvdbid取自現有資料、logid=0、管制碼=0、增刪修/選擇空白、
輸入日期/人員/地點自動帶入。- 防呆:程式代號/選單序號重複、
prgsct1無資料、@建立單號=1卻缺@資料表名稱
皆會
RAISERROR並中止;全程包在BEGIN TRAN+SET XACT_ABORT ON。呼叫範例
EXEC dbo.usp_新增程式 @程式代號 = N'CHKRB', @程式名稱 = N'標準成本與進貨比較B', @序號 = N'M-11', @按鈕名稱 = N'K.成本管理', @說明事項 = N'標準成本與進貨比較B', @資料表名稱 = N'chkrb_m', @前置碼 = N'CB';Migration Notes
- 目前僅部署於 darbnogicsa1(VFP 原始表名
_menum/_menud/_sheetno/_tableseq/prgsct1)。
- 其他客戶庫若採
twork_sys_*前綴 schema(如 darbzdcsa1),欄名與 identity 設定不同,
需另寫版本,不可直接套用本程序。
-
06-01 secretbrowsepage-detail-radgridview ▸
SecretBrowsePage 明細改用 TsBrowseRadGridView
摘要
TsERP/BrowsePage/SecretBrowsePage.xaml(權限瀏覽頁)的明細 grid 從舊的TsControl:TsDataGrid改成tsTelerik:TsBrowseRadGridView,與主檔一致,並對齊通用瀏覽頁TsControl/Layout/ErpBrowse.xaml的 master/detail pattern。變更
- 明細 grid 由
TsControl:TsDataGrid→tsTelerik:TsBrowseRadGridView(x:Name="DgDetail")。
- 改綁
DetailViewModel(TsBrowseDataGridViewModel)的DataView/Schemas,AutoGenerateColumns="True"依 DB schema 動態產生欄位。
- GridSplitter 加上
Visibility="{Binding DetailViewModel.Visibility}",與通用瀏覽頁一致。
- 移除明細寫死的 35+ 個
DataGridTextColumn/DataGridTemplateColumn(Allow* 權限 checkbox)與TsView.Resources內僅供舊欄位用的HeaderCenter、Chkstyle。
修正
- 舊明細 grid 綁
ItemsSource="{Binding DataTableItemsSource}",但其 DataContextDetailViewModel是TsBrowseDataGridViewModel,該型別沒有DataTableItemsSource屬性(只有TsDataGridViewModel才有)。主檔早先已遷移到 browse VM,明細卻未同步,導致明細實際綁不到資料。本次一併修正。
Breaking Changes
無。明細為唯讀瀏覽,欄位改由 schema 動態產生(與其他模組瀏覽頁行為一致);權限旗標以欄位值呈現,不再是唯讀 checkbox。
Migration Notes
無需資料庫或設定變更。
- 明細 grid 由
-
06-01 radgridview-session-sort-persist ▸
TsRadGridView 臨時排序跨畫面切換自動保留
摘要
TsControl/TsTelerik/TsRadGridView.cs新增「執行階段版型暫存」機制:使用者在 grid 上臨時排序(或調欄寬、欄序、群組)後,切到別的畫面再切回來時排序會自動保留,不需要手動點右鍵的「記憶格式」。此機制對全 App 所有TsRadGridView(含繼承的TsBrowseRadGridView)一體生效。背景
本專案切畫面採「換 VM + DataTemplate 重建畫面」模式,切回來時 grid 是全新實例。Telerik
RadGridView的排序狀態(SortDescriptors)只活在該 grid 控件實例上、不在資料來源(DataManager.GridView)上,且原本只有按「記憶格式」(Serialize()寫入 SQL) 才會被保存。因此臨時排序在切畫面後就消失。新增
- 靜態記憶體快取
_sessionFormatCache(Dictionary<string,string>,key =TsDataGridTag=ProgramClass + grid 名稱)。
SaveSessionFormat():Unloaded時用現成的PersistenceManager.Save把目前版型(含排序/欄寬/欄序/群組)序列化存入快取。
變更
TsRadGridView_Unloaded在解除事件掛勾後,多呼叫一次SaveSessionFormat()。
LoadSetting():在讀完 SQL 永久版型後,再套用 session 快取的版型(較新,覆蓋上去),讓切回來的新 grid 還原臨時排序。
行為
- 純記憶體、不寫 SQL:不影響、也不取代「記憶格式」功能(永久保留仍走「記憶格式」→ SQL)。
- App 關閉後快取清空(下次開啟回到 SQL 永久版型或預設)。
- 套用順序:SQL 永久版型 → session 暫存版型(session 較新者勝)。
- 因走整份
PersistenceManager版型,連同欄寬/欄序/群組也會一併在切換間保留,而不只是排序。
Breaking Changes
無。屬於行為增強;既有「記憶格式」(SQL) 流程與資料不受影響。
Migration Notes
無需資料庫或設定變更。
- 靜態記憶體快取
-
06-01 operationpanel-ctrl-f12-my-input ▸
2026-06-01 — 操作面板 Ctrl+F12:查詢「我輸入過的資料」
摘要
在表單操作面板新增鍵盤快捷鍵 Ctrl+F12:以「輸入人員 = 目前登入者」為條件,用
DARB_BROWSE的最近輸入筆數模式跑查詢,列出「我最近輸入的 20 筆、依輸入日期由新到舊」,結果直接灌進導覽區的「瀏覽」分頁,停留在目前表單頁面、不切換到瀏覽頁。讓使用者快速看到自己在該表單剛建立過的單據。新增
OperationPanelViewModel.QueryMyInputCommand(ViewModel/Control/PageParts/OperationPanelViewModel.cs)
- 條件:
輸入人員 = N'{Context.User.UserName}'(即「我建的單」)。- 查詢模式:
BrowseQuery(..., lpP3:"1", lpP4:"20"),即DARB_BROWSE @browsecondtype=1(最近輸入筆數)→SELECT TOP 20 ... WHERE 輸入人員=我 ORDER BY 輸入日期 DESC。- 重用既有瀏覽查詢路徑(
BrowseBll.BrowseQuery→DARB_BROWSE→ SQLite 虛擬化IBrowseProvider),主檔表名換成瀏覽用 view(SheetNoMethod.GetBrowseViewName),與ErpBrowseBaseViewModel.GetTablesName一致。- 結果寫進
SupportPanelViewModel.TsBrowseDataGridViewModel,作法比照TsSeekTextBoxViewModel.Seek的 "B"(瀏覽)分支。- 順便把導覽模式切到「瀏覽」:設定
DataNavigatorViewModel.NavigationModeEnum = Browse,等同勾選導覽列的「瀏覽」radio——其 setter 會自動SwitchToBrowseTab(),且讓上一筆/下一筆/第一筆/最後一筆導覽鈕改為操作這份查出來的瀏覽清單(在清單內移動並帶動表單定位)。- 防呆:主檔表為空、或目前在增刪修編輯狀態時不動作(避免覆蓋輸入中的內容);查無資料時於狀態列提示
MessageEnum.NoData。ErpProgramLayout.xaml新增KeyBinding(Key=F12、Modifiers=Control)綁定OperationPanelViewModel.QueryMyInputCommand。放在表單版面層(表單內容與操作面板的共同祖先),焦點在表單任何欄位時都能觸發,作法比照ERPSystemContainerView的全域快捷鍵。
變更
BrowseBll(LogicBll/BrowseAndQuery/BrowseBll.cs)參數化LP_P3/LP_P4:BrowseQuery與GetSqlIntoSqlite原本把DARB_BROWSE的@browsecondtype寫死"2"、@查詢筆數寫死"0",改成可傳參數lpP3="2"、lpP4="0"(預設值不變,現有呼叫端行為不受影響)。讓呼叫端可選lpP3:"1"(最近輸入筆數)並指定lpP4的 TOP N。
- 保底:當
lpP3="1"但lpP4="0"時,自動把lpP4改成"1",避免SELECT TOP 0查不到任何資料。- 方法上方補了完整的 XML 參數說明(
lpP3/lpP4對應的 proc 參數與兩種模式行為)。Breaking Changes
- 無。
備註
- 整合方式為使用者確認後的決策:採「重用既有瀏覽查詢(DARB_BROWSE)」而非直接用
darb_getdata的 DataSet——因為瀏覽格是虛擬化、由 SQLite provider 讀取,無法直接吃darb_getdata的 DataSet;並要求不切換頁面,故灌進導覽區「瀏覽」分頁。
- 建置:
ViewModel與TsControl專案以 VS MSBuild 建置通過(dotnet build因 Office COM 參考的MSB4803 ResolveComReference無法用,需用 VS 版 MSBuild)。TsERPapp 目前另有與本改動無關的 XAML 編譯錯誤(TsERP/Other/Psheet_2.xamldg-to-radgrid 遷移半成品,TsRadGridView.Columns套到TsDataGrid),非本功能造成。
- 前提假設:主檔瀏覽 view 含
輸入人員欄位(標準審核欄位)。若某程式的瀏覽 view 無此欄位,查詢會丟例外並於狀態列顯示錯誤訊息(已 try/catch)。
-
06-01 extension-panel-audit-fields ▸
擴充面板 Popup 新增建檔/輸入/工作流程/聯絡單欄位
摘要
TsControl/PageParts/ExtensionPanel.xaml與TsControl/PageParts/FinancialExtensionPanel.xaml的「加號展開 Popup」中,原本只顯示審核日期 / 審核人員,新增 6 個欄位:建檔日期、輸入日期、輸入人員、輸入地點、工作流程、聯絡單id。新增
兩支面板的 Popup grid 由 2 列擴成 8 列,於審核資訊下方依序新增(皆唯讀):
欄位 控件 備註 建檔日期 TsTextBox 唯讀 string 輸入日期 TsTextBox 唯讀 DateTime→StringFormat=yyyy/MM/dd輸入人員 TsTextBox 唯讀 輸入地點 TsTextBox 唯讀 工作流程 TsTextBox 唯讀 聯絡單id TsTextBox 唯讀 int→StringFormat=N0、靠右- 全部綁
Content.MasterItem.<欄位>,label 用DisplayMeta[<欄位>](FallbackValue 給中文),與既有審核日期/審核人員完全同模式。
- 標籤顏色沿用各面板既有主色:
ExtensionPanel用ImportantColor、FinancialExtensionPanel用SystemGenerateColor。
變更
- 新增欄位皆設
IsReadOnly="True":建檔、輸入、工作流程、聯絡單id 屬系統審核資訊,不開放手動編輯(審核日期/審核人員維持原樣可編輯)。
Breaking Changes
無。屬 UI 顯示增強,不影響資料與既有審核欄位行為。
Migration Notes
- 無需資料庫或設定變更。
輸入日期/輸入人員/輸入地點在SourceBase(每個 master poco 皆有);建檔日期/工作流程/聯絡單id僅存在於部分 Source 類別。
- 面板綁定的
Content.MasterItem為object(任意 master poco),採「字串路徑綁定」:poco 有該屬性即顯示值,沒有則顯示空(FallbackValue)並在 debug 輸出留 binding 訊息,不會 crash。此行為與既有已審核/審核日期/審核人員(同樣非 SourceBase 通用欄位)一致。
- 若要僅在具備這些欄位的特定畫面顯示,可比照
FinancialExtensionPanel對相關單號/傳票號碼的Visibility綁定方式另行控制。
- 全部綁
-
06-01 crash-recovery-no-popup-loop ▸
畫面 render 崩潰不再無限跳窗,改為自動移除壞畫面
摘要
修正當某個程式畫面在 render 途中拋例外(典型如
XamlParseException)時,錯誤視窗會無限重複跳出的問題。改為:同一錯誤只跳一次窗,隨即把壞掉的畫面從導覽移除、清空主內容區,讓 app 繼續可用,而不是反覆洗版或直接關閉整個程式。背景(原因)
「一直跳窗」並非崩潰本身造成,而是復原流程的缺陷:
1. 壞掉的 view 仍是容器(
ErpSystemContainerViewModel)的當前內容,沒有被移除。2. 錯誤用 modal
MessageBox呈現,對話框關閉時觸發主視窗 relayout → 又 render 壞 view → 再拋例外 → 再跳窗,形成迴圈。3.
App_OnDispatcherUnhandledException的迴圈偵測原本用「500ms 內才算重複」的時間窗,但 modal 對話框顯示時間必然 > 500ms,導致重複計數每次被重置、永遠到不了關閉門檻 → 無限跳窗。變更
TsERP/App.xaml.cs—App_OnDispatcherUnhandledException
- 迴圈偵測由「時間差」改為「例外簽章(型別 + 訊息)」比對,不再被 modal 對話框時間打亂。
- 同一錯誤只在第一次跳窗,之後重複只寫 log,不再洗版。
- 每次處理後呼叫復原(見下);同簽章持續重複且復原無效並超過門檻時,才顯示「即將關閉」並
Shutdown。- 新增
TryRecoverFromBrokenView(),從 DI 取IProgramNavigator並呼叫復原(全程 try/catch,復原本身不會再炸)。新增
Common/Interface/IProgramNavigator.cs— 新增bool RecoverFromBrokenCurrentView()。
ViewModel/ProgramNavigator.cs— 實作RecoverFromBrokenCurrentView():把壞掉的當前程式從_programContexts移除、_currentProgramClass設為 null,再SetContainerContext(true)將容器內容清成 null。壞畫面離開視覺樹後 render 迴圈停止;主視窗導覽列保留,使用者可改點其他程式,無需關閉整個 app。下次重新點選該程式會重新建立 context。
Breaking Changes
無 API 破壞性變更(
IProgramNavigator為內部介面,新增方法已在唯一實作ProgramNavigator補齊)。屬全域例外處理策略的行為變更。行為變化與取捨
- 之前:畫面 render 崩潰 → 錯誤視窗無限重複跳出(且因時間窗 bug,原訂的「重複 N 次自動關閉」實際上幾乎不會觸發)。
- 之後:跳一次錯誤訊息 → 自動移除壞畫面、app 續用;真正持續無法復原才關閉。
- 取捨:同一個壞掉的程式若立即再次點選,因例外簽章相同只會靜默復原、不再跳窗(log 仍會記錄)。使用者已看過一次錯誤,可接受。
Migration Notes
無需資料庫或設定變更。
📅 2026 年 5 月 (40 篇)
-
05-29 sppdt-vfp-fixes ▸
2026-05-29 — sppdt(廠商報價)VFP 對比修正
摘要
對比 VFP
scx_pur.vcx的sppdt(廠商報價)後,補回allowsave中三段遺漏的存檔邏輯到SppdtSave。變更
- 無幣別/匯率時預設本幣 + 1:存檔時若
幣別為空且匯率未填(<=0),自動帶入本幣(Context.Env.LocalCurrency)與匯率1(SppdtSave.HandleDataBeforeEndEdit)。未採用 VFP 的U_BLRATE匯率表查詢/無條件覆寫(業務決策)——外幣報價的匯率由使用者自行輸入,已輸入者不覆寫。
- 空明細列自動標記刪除:存檔時,
品號與工程代號皆為空白的明細列自動標增刪修='D',對應 VFPREPLACE 增刪修 WITH 'D' FOR EMPTY(品號) AND EMPTY(工程代號)(SppdtSave.HandleDataBeforeEndEdit)。
新增
- 活動期間驗證:已輸入「活動起始日」但未輸入「活動截止日」時擋存並提示(
SppdtSave.AllowSave),對應 VFP allowsave 同名檢查。
Breaking Changes
- 無。只有在「幣別為空且匯率未填」時才補本幣 + 1;使用者已輸入的幣別/匯率不會被覆寫(業務決策,與 VFP 無條件
U_BLRATE覆寫不同)。
- 備註:新增時
SppdtViewModel.AfterAddNew已預設幣別=本幣、匯率=1,故此存檔保險僅在資料幣別被清空等邊界情況才會觸發。
備註
- VFP 另有事件層邏輯未移植(次要):
幣別.LostFocus即時帶匯率(改由存檔時統一帶)、工程代號對 BOMproces的存在性驗證(VFP 僅提示不擋)。品號歷史單價查詢新版已由 detail listener →QueryCommand涵蓋。
- 建置:
dotnet build LogicBll通過(0 錯誤)。SaveBll 行為面建議由使用者實測:新增→輸外幣→存,確認匯率被帶入;輸活動起始日但留空截止日→存,確認擋下。
- 無幣別/匯率時預設本幣 + 1:存檔時若
-
05-29 rp-m-schema-extend ▸
2026-05-29 — 收付款/財務單(rp_m / rp_d / rpsht)schema 擴充
摘要
替 AR/AP 財務單主子檔(
rp_m/rp_d)與收付款單主子檔(rpsht_m/rpsht_d)新增多個欄位(稅額、含稅金額、備註、建檔日期、第三組單別/單號等),並同步更新對應的 4 個 view 把新欄位帶出來。對應 SQL 腳本:C:\temps\sqlscript\更新rp_m.sql。新增
資料表欄位
rp_m:建檔日期(char 10)、原幣稅額(decimal 19,4)、總原幣未稅(decimal 19,4)、總本幣未稅(decimal 19,4)、含稅別(char 1)
rp_d:原幣稅額、折扣、代工價含稅、其他金含稅、微調原含稅、微調本含稅(皆 decimal 19,4)、備註(nvarchar 120)
rpsht_m:建檔日期(char 10)
rpsht_d:分類索引4(nvarchar 30)、單別3(char 2)、單號3(nvarchar 20)
View 更新(帶出上述新欄位 + sp_refreshview)
vw_rp_m:加建檔日期、原幣稅額、總原幣未稅、總本幣未稅、含稅別
vw_rp_d:加原幣稅額、折扣、代工價含稅、其他金含稅、微調原含稅、微調本含稅、備註
vw_rpsht_m:加建檔日期
vw_rpsht_d:加分類索引4、單別3、單號3(備註改抓rp_m.備註)
變更
- 無(既有欄位/邏輯未改)
刪除
- 無
Breaking Changes
- 無。新欄位皆
NOT NULL DEFAULT,既有資料列以預設值回填,舊程式不受影響。
Migration Notes
- 執行腳本
更新rp_m.sql(依序:ALTER TABLE 加欄位 → ALTER VIEW 4 支 →sp_refreshview)。可整段執行。
- 已修正:原
rpsht_d的單別3/單號3重複 ALTER 已移除;rp_d.備註的 DEFAULT 已由0改為''。
- 對應的 Model/Source(
Twork_arp_*等)若要使用新欄位,需另跑/db-model-compare補上屬性後才會在程式端可用。
-
05-29 remove-rp-m-receipt-xml ▸
2026-05-29 — 移除 RP_M 收據xml 欄位(停用 9B 收據/發票 XML)
摘要
移除 RP_M(rp_m)的
收據xml欄位與其相關的發票 XML 序列化/還原流程。影響財務類別 9B 單據的發票號碼重建。詳見 ADR 0025。變更
- 付款/收款/暫存存檔不再產生
收據xml。
- 9B 單據轉單(RpfZero / Psheet_1)不再從
收據xml還原並覆寫多行「發票號碼」。
- Psheet_1 票據檢核/現金流程的發票號碼累計(
invoiceNO/receiptCount)不再填值(恆為空 / 0)。
新增
無。
刪除
Twork_arp_vw_rp_mModel/Twork_arp_vw_rp_mSource的收據xml屬性。
- 6 處寫入(含
FillXML產生器)、5 處讀取邏輯、語系項ReceiptXML。
- (順帶)Source 端誤掛在
收據xml上的[TsColumnName(使用人)]重複對應。
Breaking Changes
- 9B 單據不再自動把多發票合併顯示在「發票號碼」。
- 依賴 Psheet_1 票據發票號累計的下游(票據分頁/票面發票號)需確認影響。
Migration Notes
- DB 的
收據xml欄位由 DBA 自行處理(移除或保留皆可,程式已不讀寫)。
- DB 內既有的 收據xml 資料不再被使用。
- 付款/收款/暫存存檔不再產生
-
05-29 purch-special-edit ▸
2026-05-29 — purch(採購單)補回「特殊修改」
摘要
對比 VFP
scx_pur.vcx的purch(採購單)發現「特殊修改」按鈕(VFPelse8click,OPMODE=9)在新版未接線——PurchViewModel未宣告IOther3,姊妹類ppurch/fsheet都有。本次補上。變更
- 無
新增
- 特殊修改功能:
PurchViewModel新增IOther3介面與ExtendedFunction3(),呼叫框架AdeViewModel.SpecialEdit(),對應 VFPpurch.else8click(特殊修改 / OPMODE=9)。與姊妹類FsheetViewModel.ExtendedFunction3寫法一致。
Breaking Changes
- 無。新增一顆「其他」功能按鈕(特殊修改);按鈕標題由程式參數/權限設定(資料端)控制。
SpecialEdit刻意比一般編輯寬鬆、不走AllowEditHandle(與 VFP else8 未做 AllowEdit 防呆一致),故未額外加防呆。
備註
- 建置:
dotnet build ViewModel僅剩既有的MSB4803 ResolveComReference工具鏈錯誤(與本改動無關),無 C# 編譯錯誤;最終請在 Visual Studio 內 build 確認。
- purch 仍有未移植項待處理(屬功能新建,本次未動):修改交期(VFP
_CHANGEDATE)、進貨查詢(VFPPURCH_S)、確認(VFP else7,蓋確認章;現有PurchBll.Confirm()為 PO/訂單量差異報告,語意不同且未接線)。見 memoryscx-pur-compare-progress。
-
05-27 sliya5-void-stamp ▸
2026-05-27 傳票 A5 報表補回「本張作廢」章與簽核欄
摘要
比對 VFP 舊報表
sliya5.frx與 FastReport 版Sliy_a5.frx,發現新版漏移植了「本張作廢」作廢章、簽核欄與輸入人員。本次補齊,行為對齊 VFP。新增
- 本張作廢章:傳票
已審核 = 'X'(作廢)時,傳票報表頁首印出紅色作廢章。對齊 VFPsliya5的列印條件已審核='X'。三個版型皆已加入:
-
Sliy_a5.frx(A5)、Sliy.frx(中一刀)→「本張作廢」-
Sliy_e.frx(英文 Letter)→「VOID」- 簽核欄:A5 版(
Sliy_a5.frx)頁尾加「核准/覆核/會計/出納/製單」簽名列。
- 輸入人員:A5 版傳票區塊顯示輸入人員。
- 英文科目名稱:
Sliy_e.frx的科目名稱欄改用英文名稱(英文科目名稱),資料原本已由GetReportSource()由accl.英文名稱帶出。
- 英文公司名稱:
Sliy_e.frx標頭公司名稱由中文[CompanyName]改綁英文[CompanyNameEng](取代,英文報表只顯示英文)。FastReportExtension.SetDefaultReportParameter新增送出CompanyNameEng = EnvironmentSetting.CustomCompanyName(即公司英文名);該參數只有Sliy_e宣告,其他報表不受影響(SetReportParameter只對有宣告的參數設值)。
變更
Model/Rpt/Rpt_N010_SliyReportPoco.cs:新增已審核(C1)屬性。
ViewModel/N.GeneralLedger/GeneralLedger/SliyPrintViewModel.cs:GetReportSource()投影補已審核 = a.已審核,讓匯出到 SQLiterpt_n010_sliy_report1的資料帶上作廢旗標(資料原本已在 masterTworkAccVwSliymRpt.已審核,只是投影時漏掉)。
FastReport/Sliy_a5.frx:資料源加已審核欄;新增TextVoid_BeforePrintscript 依已審核 == "X"控制作廢章顯示;GroupHeader加作廢章與輸入人員;GroupFooter加簽核欄並加高 band。
刪除
無。
Breaking Changes
無。
Migration Notes
- 「作廢」判定沿用 VFP 的
已審核 == "X"。若新系統實際以其他值/欄位標記傳票作廢,需同步調整三張.frx的TextVoid_BeforePrint判斷與後端投影。
Sliy_e.frx的作廢章文字採用英文「VOID」;如需與中文版一致改回「本張作廢」,改該物件Text即可。
- A5 版的簽核欄/輸入人員尚未套用到
Sliy.frx、Sliy_e.frx(本次僅依需求補作廢章);如需一致,比照Sliy_a5.frx加物件。
- 各物件座標為對齊現有版面的擺放,可於 FastReport 設計器微調。
- 本張作廢章:傳票
-
05-27 ppurch-vfp-fixes ▸
2026-05-27 — ppurch(請購單)VFP 對比修正
摘要
對比 VFP
scx_pur.vcx的ppurch(請購單)+ 其開啟的子表單_CSAEDIT(請購單改期別)後,修正一個靜默 binding bug,並補回期別自動分類邏輯。變更
- 請購備註欄位修正:主檔「備註」輸入框原本綁定
{Binding 備註},但 Source 屬性實際是請購備註→ 該欄永遠空白且存不進去;改綁請購備註(Ppurch.xaml)。
- 期別自動分類(補回
_CSAEDIT邏輯):存檔時,若主檔已設「本期」起始日,依各明細的建議行動日落在哪個期間,自動把期別設為 本期 / 下一期 / 下二期 / 其他(PpurchSave.HandleDataBeforeEndEdit)。
- 期別日期遞增驗證:存檔時,若有設期別,檢查 本期A2≥本期A1、下一期A1>本期A2、… 等遞增關係,不符則擋存(
PpurchSave.AllowSave)。
新增
- 無
Breaking Changes
- 無。期別分類與遞增驗證都僅在主檔「本期a1」有值(期別功能使用中)時才生效,未使用期別的請購單存檔行為不變。
備註
- VFP
_CSAEDIT另有「輸入本期A1 自動帶下一/下二期日期(+30/+31/+150/+151/+300 天)」功能,因該天數為特定客戶(凱琦)專屬設定,本次未移植;使用者自行輸入三組期別日期即可,期別分類照樣運作。
- ppurch「轉訂單」(訂單轉請購 / OrmastToPP) 原為半成品,本次重建完成(照 VFP
ODTOPP):OrmastToPPBll.SpecialAdeQuery改接既有 SPDARB_TUR_ODTOPP(手動將中文欄位 map 成英文 Poco),SetUpdateSource補上按「訂單×廠商」分組建立 ppurma/ppurde(金額用既有SetFxRate()/SetAmount());新增Procedure.DARB_TUR_ODTOPP。程式碼編譯乾淨,但尚未 runtime 測試,且外幣匯率取得與分組策略有 caveat(見 memoryscx-pur-compare-progress)。
- 請購備註欄位修正:主檔「備註」輸入框原本綁定
-
05-27 bankfcst-spbank-vfp-fixes ▸
2026-05-27 — bankfcst / spbank / ftrlima / fsheet VFP 對比修正
摘要
對比 VFP
scx_chk.vcx的bankfcst(預估資金表)、spbank(廠商帳戶表)、ftrlima(財務雜項單據)、fsheet(收付款單)後,修正四個與舊系統行為不一致、UI 重複或缺少防呆的問題。另記錄一個待辦(spbank 異動轉檔)。bankacqry(銀行存提表)、bankday(資金日報表)同批對比確認為忠實移植,無需修改。變更
- bankfcst 本幣金額小數位:
存入本幣金額/提出本幣金額原本用外幣(幣別)的小數位數四捨五入,改為用本幣小數位(Context.Env.LocalDecimal),與 VFPROUND(存入金額*匯率,0)及姊妹類 checkrec 的本幣金額算法一致。外幣(例如 USD)預估資金的本幣換算金額顯示位數會因此修正。
- spbank 自訂群組自動帶值:輸入廠商代號時,若「自訂群組」為空白,自動帶入廠商代號本身(還原 VFP
廠商代號.Valid行為)。
- ftrlima 明細移除重複欄:明細 grid 原本有兩個完全相同的「需求部門」欄(複製貼上殘留),移除其中一個。
新增
- fsheet 編輯/刪除防呆(
FsheetEdit):還原 VFPfsheet.allowedit。收付款單若已有「出納傳票號」(已轉票據匯款)、「沖帳備註」(已沖帳)或「沖帳傳票號」(已轉傳票),不得修改或刪除。新版原本缺少FsheetEdit(走 baseEditBll無條件允許),與同表姊妹類PsheetEdit(應付)/RsheetEdit(應收)不一致——本次補齊。保留IsCancelStike例外(取消沖帳流程下允許編輯,對應 VFPIF NOT 取消沖帳)。
Breaking Changes
- 行為變更:先前可以修改/刪除「已轉傳票或已沖帳」的收付款單(F 類),補上防呆後將被擋下並提示。此為還原舊系統與姊妹類既有保護,屬預期行為。
備註
- VFP
fsheet.allowedit僅有上述三條轉檔/沖帳防呆;姊妹類PsheetEdit/RsheetEdit另含「禁修日期(closing book)」檢查。本次FsheetEdit先只還原 VFP 既有的三條,未加禁修日期檢查(若要與 Psheet/Rsheet 完全一致可後續再補)。
新增
- 無
刪除
- 無
Breaking Changes
- 無
Migration Notes
- 無需 DB migration。bankfcst 本幣金額為畫面即時計算(未持久化),僅影響顯示;既有資料不受影響。
待辦(規劃中)
- spbank「異動轉檔」:VFP
spbank.else1click(CMDELSE1,Caption=「異動轉檔」)會匯出台新銀行付款檔(DO FORM SPBANK_E),新版尚未實作對應IOther1.ExtendedFunction1。經確認屬公司專屬需求,暫列待辦、未排程實作。
對使用者的影響
- 「預估資金表」外幣單據的本幣換算金額顯示位數修正為本幣位數。
- 「廠商帳戶表」新增/輸入廠商代號時,自訂群組會自動帶入廠商代號(原本留空)。
- bankfcst 本幣金額小數位:
-
05-27 accself-poco-chinese-columns ▸
2026-05-27 Accself 報表資料源欄位改中文(修好空白問題)
摘要
Accself 系列報表(Accself1~10、Accselfm 等)的 FastReport
.frx都綁中文欄名,但其資料源 POCORpt_N130_AccrptReportPoco1是英文屬性名。ExportToSqlite直接用屬性名當 SQLite 欄名(GetColumnString的translateToLocalColumn實為 no-op),所以匯出的是英文欄 → 與 .frx 的中文綁定對不上 → 報表月份/金額欄空白。本次把該 POCO 屬性名改成中文(對照Model.json既有英中對照、與 .frx 欄名一致),讓資料源欄位與報表綁定吻合。變更
Model/Rpt/Rpt_N130_AccrptReportPoco1.cs:~60 個屬性英文→中文(如ReportTitle→報表抬頭、Year0And01→今年01、Budget01→預算01、DecimalLength→小數位數…)。計算屬性、當期月份setter、NeedShow()內部引用一併中文化,邏輯不變。輔助類Rpt_N130_AccrptReportPoco1Parameter維持英文(不匯出)。
Model/Bll/SelfrptSource.cs:ToRptN130物件初始化左側(POCO 屬性)改中文;右側(SelfrptSource 自身英文屬性 / para)不變。
TworkReportPluginOfficial/N.GeneralLedger/:11 支 plugin 修正對該 POCO 的引用——
- Accself1~6、9、10、Accselfm:
item.ReportTitle/QueryDate→報表抬頭/查詢日期- Accselfm、Accselfm1、Accselfm2:建立 POCO 的物件初始化左側改中文(右側
item.*為 SelfrptSource 英文屬性,不動)Accself7/8/Accselfsimple不受影響(它們的目標是另一支早已中文化的Rpt_N130_AccrptReportPoco,且讀的是 SelfrptSource 英文屬性)。
刪除
無(屬性改名,非刪除)。
Breaking Changes
- 匯出表
rpt_n130_accself_report1的欄名由英文變中文。FastReport.frx已是中文綁定,故報表端不需改;但任何直接以英文欄名讀此表的程式/查詢都需同步改中文(目前未發現此類使用)。
Migration Notes
.frx無需修改、無需重新部署到 Reports 夾。本次純後端改名,需重新編譯部署 app才生效。
- 命名以
TsERP/Lang/Model1/Model.json的Rpt_N130_AccrptReportPoco1英中對照為準,與 .frx 欄名一致。
- 同類別的其他報表 POCO(Sliy、Sliybrowd 等)本來就是中文屬性名,不受影響。
-
05-27 accself-account-name-indent ▸
2026-05-27 Accself 報表科目名稱還原前導縮排空格
摘要
Acc_selfrpt(自訂財報)報表的科目名稱在讀取 SQL 公式表時,前導縮排空格被吃掉,導致報表層級縮排消失(呈現扁平)。本次在讀取後從原始 DataTable 還原前導空格。
變更
LogicBll/N.GeneralLedger/Acc_selfrpt/Acc_selfrptBll.cs:GetTmpmoverecs在dt.ToList<AccselfrptSource>()後,從原始DataTable的「科目名稱」欄還原值(只TrimEnd、保留前導縮排),讓使用者在公式中打的層級縮排空格能呈現於報表。
根因
Common/ListDataTable/DataTableToList.cs對所有字串欄位一律value.ToString().Trim()(全域共用),把使用者在「科目名稱」前面打的縮排空格清掉。自動產生列(ACC_BSAUTO / ACC_BSCE)因為空格是在 ToList 之後用new string(' ', n)補的,所以不受影響——故「自動列有縮排、自訂列沒有」。影響範圍
- 僅 Acc_selfrpt;未改動全域
DataTableToList(避免影響其他報表/查詢)。
- 一般自訂公式列(
FormulaGenerator.GenerateFormula的 else 直通分支)會帶回縮排。
Migration Notes
- 純後端修正,
.frx無需修改、無需重新部署 Reports 夾;需重新編譯部署 app 才生效。
-
05-26 asset-amortization-monthly ▸
2026-05-26 — 財產資料(ASSET) 折舊計算與預留殘值
摘要
財產資料的「計算折舊」改回與舊系統一致的月折舊法;輸入「年限」時自動帶出建議的「預留殘值」。對應 VFP
scx_ast對比結果。變更
- 計算折舊算法:由逐日比例攤提改為月折舊(年折舊額/12、年底補差、最後一期補總差),折舊明細金額分佈與舊系統一致。
- 年限 → 預留殘值自動帶入:在「金額與年限」區改「年限」並離開欄位時,自動填入
預留殘值 = 四捨五入(取得原價(或改良成本) ÷ (年限+1)),可手動覆寫。新增與修改模式皆會觸發。
新增
無新欄位 / 無新畫面。
刪除
無。
Breaking Changes
無。既有已存檔的折舊明細不受影響。
Migration Notes
- 不需 migration。
- 若某筆財產要套用新(月折舊)算法,請重新按「計算折舊」重產折舊明細。
-
05-24 sliy-budget-project-cascade ▸
2026-05-24 — Sliy / Budget 主表專案代號自動帶入明細 + 顯示專案簡稱
摘要
Sliy(總帳傳票)與Budget(預算傳票)兩支畫面:主表「專案代號」變更時,自動把專案代號與專案簡稱覆蓋寫入所有現存明細列;新增明細列時也自動帶入。明細區下方說明區新增「專案簡稱」欄位。Budget 明細表 grid 補上原本沒有的「專案代號 / 專案簡稱」兩欄,跟 Sliy 對齊。新增
ViewModel/N.GeneralLedger/GeneralLedger/SliyVirtual.cs:
-
CascadeProjectToAllDetails()— master 專案代號a 變更時,查Vwseek_aproj取得專案簡稱後,覆蓋所有增刪修 != "D"的明細列-
DetailListenerHelper_OnAddListener()— 新增明細列時依 master 專案代號a 帶入;以AdeViewModel.Status == Normal防呆,避免載入舊資料時誤觸發 DB 查詢Model/Source/N.GeneralLedger/Twork_acc_vw_sliyyearmSource.cs:補專案代號a屬性(純記憶體 stub;該年度匯總類別 DB 端無此欄位,僅為滿足ISliymSource)
TsERP/N.GeneralLedger/Sliy.xaml:下方說明區(DetailSelectedItem)新增 Row 4:專案簡稱 label + read-only textbox
TsERP/N.GeneralLedger/Budget.xaml:
- 明細 grid 新增
TsArpojSeekGridViewColumn(專案代號 seek)+ 專案簡稱顯示欄- 下方說明區新增 Row 4:專案簡稱
變更
Model/Source/N.GeneralLedger/ISliymSource.cs:interface 新增string 專案代號a { get; set; }
Model/Source/N.GeneralLedger/ISliySource.cs:interface 新增string 專案代號 { get; set; }與string 專案簡稱 { get; set; }
SliyVirtual建構子:
- 給
detailListenerHelper掛上OnAddListener事件- 新增
masterListenerHelper(監聽ISliymSource.專案代號a)並呼叫MasterData.AddListener()SliyVirtual.ReceiveWeakEvent():增加sender is T && PropertyName == 專案代號a分支,觸發CascadeProjectToAllDetails()
刪除
無
Breaking Changes
ISliymSource/ISliySource介面擴增屬性:所有實作這兩個介面的類別必須有對應屬性。本次掃過的所有實作(Twork_acc_vw_sliymSource、Twork_acc_vw_budgetmSource、Twork_acc_vw_sliySource、Twork_acc_vw_budgetSource、Twork_acc_vw_sliyyearSource、Twork_acc_vw_sliyyearmSource、Sliy1Source)皆已有或補上對應屬性。外部專案若有自寫實作需自行調整
Migration Notes
無 schema 變更;DB 端的
專案代號欄位本來就存在於 detail 表,本次只是把主表的值同步進去。既有資料不受影響。對使用者的影響
- Sliy:
- 主表輸入或修改「專案代號」後,所有明細列的「專案代號 / 專案簡稱」立即被覆蓋為 master 同值(包含使用者自行調整過的列)。如需明細列各自設定不同專案,請在 master 設好後再個別調整明細
- 下方說明區點選明細列後可看到該列的「專案簡稱」
- Budget:
- 同 Sliy 行為
- 明細表 grid 多出兩欄「專案代號 / 專案簡稱」(先前 Budget grid 沒有露出這兩欄)
- 載入既有傳票時不會觸發 cascade(僅在 Add / Edit 狀態下觸發),不會造成回歸
-
05-24 btn-styles-unify ▸
2026-05-24 — 操作按鈕 Style 統一基準
摘要
TsControl/Themes/Generic.xaml內 6 個TsButtonStyle(BtnSave / BtnReturn / BtnTurn / BtnConfirm / BtnCancel / BtnExit)統一視覺基準與內容寫法,並把唯一漏掉多語系的BtnConfirm文字改成 DisplayMeta 綁定。變更
- 共通基準:6 個按鈕全部對齊
Height="40"+HorizontalContentAlignment="Left"
-
BtnSave補上HorizontalContentAlignment="Left"-
BtnTurn補上Height="40"與HorizontalContentAlignment="Left",移除 Image / TsTextBlock 多餘的HorizontalAlignment="Center"- 內容寫法統一改用
ContentTemplate:BtnConfirm/BtnCancel/BtnExit原本用<Setter Property="Content">直接內嵌StackPanel,已改成ContentTemplate+DataTemplate,避免同一個 UI 元件實例被多顆按鈕共用而觸發 WPF「元件不可有多重父容器」例外
BtnConfirm文字多語系化:Text="Confirm"改為Text="{Binding ElementName=View, Path=DataContext.DisplayMeta[Confirm], FallbackValue=Confirm}"
Migration Notes
- 若有專案資源覆寫
BtnConfirm的 Content Template,請改寫成ContentTemplate
Common/Lang/Xaml/與TsERP/Lang/Xaml1/Xaml.json需確認Confirmkey 已存在(既有其他按鈕已用,理論上已有)
影響檔案
TsControl/Themes/Generic.xaml
未處理(後續)
BtnConfirm圖示目前是返回00.png(與 BtnReturn 同),疑似 copy-paste 錯誤;本次依使用者指示先不動
- 共通基準:6 個按鈕全部對齊
-
05-24 bankflow-chart-fixes ▸
2026-05-24 — Bankflow 全部銀行帳戶折線圖顯示修正
摘要
修正 2026-05-22 加入的「全部銀行帳戶」折線圖三個顯示問題:X 軸日期重複擠在一起、X 軸標籤重疊、Y 軸出現科學記號。
變更
ViewModel/O.Check/BankflowViewModel.cs
-
Bankflow03Chart改為依「日期」分組後取每組Last():Bankflow03是 running balance,同一天多筆交易會在同一個 X category 上重疊;只取每日最後一筆即當日收盤結餘。TsERP/O.CAandCL/Bankflow.xaml
-
CategoricalAxis加LabelInterval="5"+MajorTickInterval="5",搭配既有的LabelFitMode="Rotate",避免標籤密集重疊。-
LinearAxis加LabelFormat="{}{0:N0}",Y 軸金額顯示千分位整數,停用科學記號(如1.23E+6)。Breaking Changes
無。
Bankflow03Chart是純讀取屬性,呼叫端只有 XAML chart binding;分組後資料筆數變少但 schema 不變。Migration Notes
無 DB / 設定遷移。重新 build TsERP 即生效。
相關
- [[Changelog 2026-05-22 — Bankflow Baseline + Chart]](2026-05-22-bankflow-baseline-chart.md)(原始功能)
- [[ADR 0021 — Bankflow chart 採用 Telerik RadCartesianChart]](../decisions/0021-bankflow-chart-telerik.md)
-
05-24 asset-accexp-workflow-fields ▸
2026-05-24 — 固定資產 asset / accexp 加工作流程欄位 + view 同步
摘要
固定資產模組(
Q.FixedAsset)的asset(財產主檔)與accexp(費用主檔)兩張表統一加入工作流程簽核 6 欄(建檔日期/聯絡單id/工作流程/審核人員/審核日期/已審核),讓固定資產主檔也能掛上簽核流程,並同步更新vw_asset/vw_accexpview 加入新欄位。延續 [[2026-05-22-cheque-system-workflow-fields]] 票據系統的相同 pattern。新增
asset/accexp各新增以下 6 欄(搭配 Source/Model 已有對應屬性):
-
[建檔日期] char(10) NOT NULL DEFAULT ''-
[聯絡單id] int NOT NULL DEFAULT 0-
[工作流程] varchar(6) NOT NULL DEFAULT ''-
[審核人員] nvarchar(20) NOT NULL DEFAULT ''-
[審核日期] varchar(10) NOT NULL DEFAULT ''-
[已審核] char(1) NOT NULL DEFAULT ''變更
vw_assetview 重建,SELECT 清單末端加入建檔日期/聯絡單id/工作流程/審核人員/審核日期/已審核
vw_accexpview 重建,SELECT 清單末端加入相同 6 欄
- 兩個 view 重建後執行
sp_refreshview刷新中介資料
刪除
- 無
Breaking Changes
- 無。所有新欄位
NOT NULL DEFAULT,既有 row 直接補預設值
Migration Notes
SQL Server 端必跑 — 升級資料庫到本 commit 前先執行以下 script:
ALTER TABLE accexp add [建檔日期] [char](10) NOT NULL DEFAULT ''; ALTER TABLE accexp add [聯絡單id] [int] NOT NULL DEFAULT 0; ALTER TABLE accexp add [工作流程] [varchar](6) NOT NULL DEFAULT ''; ALTER TABLE accexp add [審核人員] [nvarchar](20) NOT NULL DEFAULT ''; ALTER TABLE accexp add [審核日期] [varchar](10) NOT NULL DEFAULT ''; ALTER TABLE accexp add [已審核] [char](1) NOT NULL DEFAULT ''; ALTER TABLE asset add [建檔日期] [char](10) NOT NULL DEFAULT ''; ALTER TABLE asset add [聯絡單id] [int] NOT NULL DEFAULT 0; ALTER TABLE asset add [工作流程] [varchar](6) NOT NULL DEFAULT ''; ALTER TABLE asset add [審核人員] [nvarchar](20) NOT NULL DEFAULT ''; ALTER TABLE asset add [審核日期] [varchar](10) NOT NULL DEFAULT ''; ALTER TABLE asset add [已審核] [char](1) NOT NULL DEFAULT ''; go ALTER VIEW [dbo].[vw_asset] AS SELECT dbo.asset.財產編號, dbo.asset.資產別, dbo.asset.財產名稱, dbo.asset.財產分類碼, dbo.asset.廠商代號, dbo.spdata.廠商簡稱, dbo.asset.廠牌, dbo.asset.規格, dbo.asset.單位, dbo.asset.部門代號, dbo.asset.數量, dbo.asset.取得日期, dbo.asset.取得原價, dbo.asset.改良成本, dbo.asset.預留殘值, dbo.asset.現有狀況碼, dbo.asset.年限, dbo.asset.備註, dbo.asset.資產減科目, dbo.asset.費用科目, dbo.asset.處分日期, dbo.asset.再提年限, dbo.asset.處分金額, dbo.asset.再提殘值, dbo.asset.輸入日期, dbo.asset.輸入人員, dbo.asset.輸入地點, dbo.asset.增刪修, dbo.asset.選擇, dbo.asset.管制碼, dbo.asset.srvdbid, dbo.asset.pkid, dbo.asset.logid, dbo.asset.linkid, dbo.dept.部門簡稱, dbo.asset.保固年限, dbo.asset.原始金額, dbo.asset.附加費用, dbo.vwcmb223_財產分類.field2 AS 財產分類, dbo.vwcmb224_現有狀況.field2 AS 現有狀況, dbo.asset.傳票號碼, dbo.asset.財務單號, dbo.asset.置放地點, dbo.asset.建檔日期, dbo.asset.聯絡單id, dbo.asset.工作流程, dbo.asset.審核人員, dbo.asset.審核日期, dbo.asset.已審核 FROM dbo.asset LEFT OUTER JOIN dbo.dept ON dbo.asset.部門代號 = dbo.dept.部門代號 LEFT OUTER JOIN dbo.spdata ON dbo.asset.廠商代號 = dbo.spdata.廠商代號 LEFT OUTER JOIN dbo.vwcmb223_財產分類 ON dbo.asset.財產分類碼 = dbo.vwcmb223_財產分類.field1 LEFT OUTER JOIN dbo.vwcmb224_現有狀況 ON dbo.asset.現有狀況碼 = dbo.vwcmb224_現有狀況.field1 GO ALTER VIEW [dbo].[vw_accexp] AS SELECT dbo.accexp.費用編號, dbo.accexp.費用類別碼, dbo.vwcmb226_費用類別.field2 AS 費用類別, dbo.accexp.費用名稱, dbo.accexp.製單日期, dbo.accexp.廠商代號, dbo.accexp.保單號碼, dbo.accexp.費用起始日, dbo.accexp.費用截止日, dbo.accexp.費用金額, dbo.accexp.資產科目, dbo.accexp.費用科目1, dbo.accexp.費用科目2, dbo.accexp.費用科目3, dbo.accexp.費用科目4, dbo.accexp.百分比1, dbo.accexp.百分比2, dbo.accexp.百分比3, dbo.accexp.百分比4, dbo.accexp.備註, dbo.accexp.輸入日期, dbo.accexp.輸入人員, dbo.accexp.輸入地點, dbo.accexp.增刪修, dbo.accexp.選擇, dbo.accexp.管制碼, dbo.accexp.srvdbid, dbo.accexp.pkid, dbo.accexp.logid, dbo.accexp.linkid, dbo.spdata.廠商簡稱, dbo.accexp.財務單pkid, dbo.accexp.傳票號碼, dbo.accexp.財務單號, dbo.accexp.部門代號, dbo.dept.部門簡稱, dbo.accexp.建檔日期, dbo.accexp.聯絡單id, dbo.accexp.工作流程, dbo.accexp.審核人員, dbo.accexp.審核日期, dbo.accexp.已審核 FROM dbo.accexp LEFT OUTER JOIN dbo.dept ON dbo.accexp.部門代號 = dbo.dept.部門代號 LEFT OUTER JOIN dbo.spdata ON dbo.accexp.廠商代號 = dbo.spdata.廠商代號 LEFT OUTER JOIN dbo.vwcmb226_費用類別 ON dbo.accexp.費用類別碼 = dbo.vwcmb226_費用類別.field1 GO EXEC sp_refreshview 'vw_accexp' EXEC sp_refreshview 'vw_asset'對使用者的影響
- 固定資產主檔(
asset)與費用主檔(accexp)開始支援工作流程簽核欄位(DB 端已備齊;UI 是否暴露簽核控制依 XAML / ViewModel 設定,搭配本次TsERP/Q.FixedAsset/Accexp.xaml調整)
vw_asset/vw_accexpview 多了 6 個欄位,下游 browse / 報表若需顯示審核狀態可直接取用
-
05-23 checkpay-checkrec-single-column-layout ▸
2026-05-23 — Bank / Checkpay / Checkrec 改 GroupBox 分區版型
摘要
銀行帳戶(
Bank.xaml)、應付票據(Checkpay.xaml)、應收票據(Checkrec.xaml)三個 Master 編輯區統一改成「外層 2 欄、每欄一個 StackPanel 堆 GroupBox」的版型,把欄位依語意分組(基本資料 / 交易資訊 / 金額匯率 / 票況 / 其他 …)。每個 GroupBox 內部維持 2 欄 Grid(label / 輸入),每組欄位各佔一個Grid.Row。先前版本把多組獨立欄位塞進同一個StackPanel(例如 Bank 的「帳戶代號 + 帳戶簡稱 + 日報表」「支票字軌 + 票號長度 + 票據號碼」、Checkpay 的「票據單號 + 公司代碼」、Checkrec 的「禁止轉讓 + 平行線」「票況 + 異動日期」),造成欄位上下對不齊、巢狀子元素還掛著無效的Grid.Row/Grid.Column屬性,這次一併清掉。變更
TsERP/O.CAandCL/Bank.xaml
- 左欄 GroupBox「基本資料 / 支票設定」、右欄 GroupBox「銀行資訊 / 會計科目 / 其他」
TsERP/O.CAandCL/Checkpay.xaml
- 第一個 tab(應付票據):左欄 GroupBox「基本資料 / 金額匯率」、右欄 GroupBox「交易資訊 / 票況 / 其他」
- 第二個 tab(LC):左欄 GroupBox「基本資料 / 日期」、右欄 GroupBox「金額匯率 / 還款利息 / 其他 / 保險」
- 修正
Orientation=" Horizontal"多空白的小瑕疵TsERP/O.CAandCL/Checkrec.xaml
- 左欄 GroupBox「基本資料 / 金額匯率」、右欄 GroupBox「開票資訊 / 票況 / 其他資訊」
- 統一帳戶代號欄位的 Margin / VerticalAlignment 風格
新增
- 無
刪除
- StackPanel 子元素上殘留的無效
Grid.Row/Grid.Column屬性
Breaking Changes
- 無。純 XAML 排版調整,未動 binding 路徑、未動
x:Name、未動 code-behind
Migration Notes
- 無需 DB / 設定遷移
對使用者的影響
- 應付票據、應收票據新增 / 編輯畫面的欄位排列依語意分區,GroupBox 標題能一眼看出該區欄位的用途(基本資料、金額匯率、票況…)
- 外層左右兩欄擺放 GroupBox,整體高度比單欄全部上下堆疊版本矮,較符合一般螢幕長寬比
DisplayMeta 多語系待補
各 GroupBox 標題以下 key 走 DisplayMeta,目前 fallback 為中文,正式上線前請在
Common/Lang/Xaml/與TsERP/Lang/Xaml1/Xaml.json補對應 JSON:基本資料、交易資訊、金額匯率、票況、其他、日期、還款利息、保險、開票資訊、其他資訊、支票設定、銀行資訊、會計科目
-
05-22 perdata-other-multi-row ▸
2026-05-22 — Perdata「眷屬資料」改為多筆 + perdata_d 序號 schema 兩位前綴
摘要
人事資料(PERDATA)的「眷屬資料」(原 Other)從單組固定欄位 key-value 表單改成多筆同 schema 表格,跟眷屬與聯絡人 / 學歷 / 任職經歷一致。同時把
perdata_d.序號的區塊前綴從一位數統一成兩位數。變更
UI / Storage 行為改變
- 「眷屬資料」grid 從 3 欄寫死(序號 / 標題 / 內容)→ 動態欄位(眷屬稱謂 / 眷屬姓名 / 眷屬出生 / 眷屬職業 / 身份證號 / 眷屬關係),一個員工可以填多筆眷屬
- 右鍵選單支援「新增數筆」(每次補 10 列空白)
- 儲存進
perdata_d的格式從「同 序號='2'、不同 欄位 共 6 筆」→「每一列拆 6 筆,序號 = '02' + 列序號 (如 '0201'/'0202')」
程式碼
ViewModel/R.HumanResource/PerdataViewModel.cs
-
Other屬性型別:List<其他資料Poco>→DataTable- 新增
otherRelatives(List<TsTemplateRelative>) +OtherAdder(IDataGridAdd)-
otherSturcuture建構移除false參數 → 走預設加IsVirtualEmpty/NeedDelete/序號系統欄-
AfterAddNew/EditBll_OnAfterEdit/AfterMoverRecord/SaveBll_OnBeforeAllowSave改成 DataTable 模式(跟其他三個一致)LogicBll/Ade/R.HumanResource/PerdataSave.cs
-
Other屬性型別:List<其他資料Poco>→TemplateDynamicDataGridPoco-
HandleDataBeforeEndEdit改用Action<T>版的Transfer,把detail.AddItem作為 callback 傳入(直接傳detail.ObservableCollection會炸Collection is read-only,因為它是ReadOnlyObservableCollection<T>)- 同步把其他三個 prefix 補 0:
"1"→"01"、"3"→"03"、"4"→"04",跟 [[2026-05-21-perdata-add-multiple-rows]] 內AfterMoverRecord已改成的兩位數StartsWith對齊- 刪除
OtherHandleprivate method 與其他資料PococlassCommon/Model/Vwseek_datatpl.cs
-
TemplateDynamicDataGridPoco.Transfer加Action<T>版 overload;舊IList<T>版改成委派到新版(呼叫detail.Add)。新版讓呼叫端能傳IDataManager.AddItem避開 ReadOnly Collection 問題- 同個 bug 也存在於
SpdataSave(傳ObservableCollection給Transfer),本次未修,視情況另外處理TsERP/R.HumanResource/Perdata.xaml
-
DgOther移除寫死的 3 個 Columns;加DataGridAdd="{Binding OtherAdder}"、ItemsSource="{Binding Other.DefaultView}"TsERP/R.HumanResource/Perdata.xaml.cs
-
Perdata_OnLoaded加new DataGridCreator(Context, "perdata", "P1", "P1_2").SetDataGridColumn(DgOther);Breaking Changes
perdata_d.序號schema 從一位前綴變兩位前綴,沒有遷移會讀不到舊資料:區塊 舊 schema 新 schema 眷屬與聯絡人 1XX01XX眷屬資料 (Other) 2(固定,6 筆同序號)02XX學歷 3XX03XX任職經歷 4XX04XXMigration Notes
對每個目標 DB 執行以下 SQL。先 備份
perdata_d整表再跑。-- 1. Contact / Education / WorkExperience:1XX / 3XX / 4XX → 0XXX (補 0 前綴) UPDATE perdata_d SET 序號 = '0' + RTRIM(序號) WHERE LEFT(RTRIM(序號), 1) IN ('1', '3', '4') AND LEN(RTRIM(序號)) BETWEEN 2 AND 3; -- 2. Other 那組:原本固定 序號='2',每個 perdata master 只有一組 6 筆 → 改成 '0201'(合併成第 1 列) UPDATE perdata_d SET 序號 = '0201' WHERE RTRIM(序號) = '2'; GO注意事項
- 程式讀回時
GetDataTableAndRelatives用序號當 group key,舊 6 筆 ('0201') 會 lookup 成同 1 個 DataRow,UI 自然顯示為「眷屬資料第 1 列已填」。後續使用者新增列從'0202'開始遞增
- 若
perdata_d.序號為char(N)固定長度,RTRIM處理尾端空白後比對應該安全;若為varchar則直接序號 IN ('1', '2', ...)也可
- migration 前先
SELECT 序號, COUNT(*) FROM perdata_d GROUP BY 序號 ORDER BY 序號確認資料現況,再決定 WHERE 條件邊界
相關
- [[2026-05-21-perdata-add-multiple-rows]] — 上一輪先把 Contacts/Education/WorkExperience 接「新增數筆」 + 修 DataTable binding error
-
05-22 inventquery-tab-column-fix ▸
2026-05-22 — InventQuery 切換 tab 後預設 column 殘留修復
摘要
InventQuery系列(客戶報價/訂單/出貨、廠商報價/採購/收貨/工單、Trwkma 等)放在TsTabControl內。原本切走 tab 再切回來時,已經 AutoGenerate 出的真實資料 column 會被「沒 binding 的預設 header column」蓋掉。改完後切回 tab 維持查詢結果的 column。根因
TsTabControl.ContentTemplate每次切換都會重建InventQuery控件樹 → 連帶重建TsBrowseRadGridView。原本「是否已生成預設 column」的旗標isGenerateDefaultColumnHeader是 grid 控件 instance 的 field,跨重建會 reset;但Schemas(在 ViewModel)保留 → 切回時OnSchemasChanged又跑GenerateDefaultColumnHeader,把預設 header-only column 加回來。新增
ViewModel.Control.TsBrowseDataGridViewModel.IsAutoGenerated屬性
- 跨 grid 控件生命週期存活(ViewModel 不會被 tab 切換重建)
-
CreateDataView()結尾設true-
SetNull()重置為false變更
TsControl/TsTelerik/TsBrowseRadGridView.cs
-
OnSchemasChanged開頭加判斷:若DataContext is TsBrowseDataGridViewModel { IsAutoGenerated: true }→ 直接 return,不再呼叫GenerateDefaultColumnHeaderViewModel/Control/TsBrowseDataGridViewModel.cs
-
CreateDataView()末尾追加IsAutoGenerated = true-
SetNull()末尾追加IsAutoGenerated = false刪除
無。
Breaking Changes
無。
IsAutoGenerated是新屬性,預設false→ 第一次查詢前的行為與舊版一致。Migration Notes
無 DB / 設定遷移。重新 build TsERP 即生效。
影響範圍
所有使用
TsBrowseRadGridView+TsBrowseDataGridViewModel+ 動態 Schemas 的查詢頁面。InventQuery系列受惠最大,其他用同一機制的 browse-grid 也順帶修復同類型潛在問題。驗收
1. 進
Invent_bs(或任何掛 InventQuery 的程式)2. 在第一個 tab 按查詢 → 看到真實資料 column
3. 切到第二個 tab → 切回第一個 tab
4. 預期:column 仍是真實資料 column(不是 header-only 預設 column)
-
05-22 cheque-system-workflow-fields ▸
2026-05-22 — 票據系統 7 張表加工作流程欄位 + bank 加票據號碼
摘要
票據系統相關 7 張表(
bank/checkpay/checkrec/bankfcst/bankfxrm/spbank)統一加入工作流程簽核欄位(建檔日期/聯絡單id/工作流程/審核人員/審核日期/已審核),讓票據主檔也能掛上簽核流程。bank表另外補上票據號碼欄位。同時修正_sheetno內BANKFXR單據別的子表 view 名稱為vw_bankfxrd。新增
- 7 張票據相關表新增以下 6 欄(搭配 Source/Model 已有對應屬性):
-
[建檔日期] char(10) NOT NULL DEFAULT ''-
[聯絡單id] int NOT NULL DEFAULT 0-
[工作流程] varchar(6) NOT NULL DEFAULT ''-
[審核人員] nvarchar(20) NOT NULL DEFAULT ''-
[審核日期] varchar(10) NOT NULL DEFAULT ''-
[已審核] char(1) NOT NULL DEFAULT ''bank表額外新增[票據號碼] nvarchar(40) NOT NULL DEFAULT ''
變更
_sheetno內單據別 = 'BANKFXR'的子表1view從原值更新為vw_bankfxrd,配合新版Twork_chk_vw_bankfxrdview 命名
刪除
- 無
Breaking Changes
- 無。所有新欄位
NOT NULL DEFAULT,既有 row 直接補預設值
Migration Notes
SQL Server 端必跑 — 升級資料庫到本 commit 前先執行以下 script:
update _sheetno set 子表1view ='vw_bankfxrd' where 單據別 = 'BANKFXR' ALTER TABLE bank add [票據號碼] [nvarchar](40) NOT NULL DEFAULT ''; ALTER TABLE bank add [建檔日期] [char](10) NOT NULL DEFAULT ''; ALTER TABLE bank add [聯絡單id] [int] NOT NULL DEFAULT 0; ALTER TABLE bank add [工作流程] [varchar](6) NOT NULL DEFAULT ''; ALTER TABLE bank add [審核人員] [nvarchar](20) NOT NULL DEFAULT ''; ALTER TABLE bank add [審核日期] [varchar](10) NOT NULL DEFAULT ''; ALTER TABLE bank add [已審核] [char](1) NOT NULL DEFAULT ''; ALTER TABLE checkpay add [建檔日期] [char](10) NOT NULL DEFAULT ''; ALTER TABLE checkpay add [聯絡單id] [int] NOT NULL DEFAULT 0; ALTER TABLE checkpay add [工作流程] [varchar](6) NOT NULL DEFAULT ''; ALTER TABLE checkpay add [審核人員] [nvarchar](20) NOT NULL DEFAULT ''; ALTER TABLE checkpay add [審核日期] [varchar](10) NOT NULL DEFAULT ''; ALTER TABLE checkpay add [已審核] [char](1) NOT NULL DEFAULT ''; ALTER TABLE checkrec add [建檔日期] [char](10) NOT NULL DEFAULT ''; ALTER TABLE checkrec add [聯絡單id] [int] NOT NULL DEFAULT 0; ALTER TABLE checkrec add [工作流程] [varchar](6) NOT NULL DEFAULT ''; ALTER TABLE checkrec add [審核人員] [nvarchar](20) NOT NULL DEFAULT ''; ALTER TABLE checkrec add [審核日期] [varchar](10) NOT NULL DEFAULT ''; ALTER TABLE checkrec add [已審核] [char](1) NOT NULL DEFAULT ''; ALTER TABLE bankfcst add [建檔日期] [char](10) NOT NULL DEFAULT ''; ALTER TABLE bankfcst add [聯絡單id] [int] NOT NULL DEFAULT 0; ALTER TABLE bankfcst add [工作流程] [varchar](6) NOT NULL DEFAULT ''; ALTER TABLE bankfcst add [審核人員] [nvarchar](20) NOT NULL DEFAULT ''; ALTER TABLE bankfcst add [審核日期] [varchar](10) NOT NULL DEFAULT ''; ALTER TABLE bankfcst add [已審核] [char](1) NOT NULL DEFAULT ''; ALTER TABLE bankfxrm add [建檔日期] [char](10) NOT NULL DEFAULT ''; ALTER TABLE bankfxrm add [聯絡單id] [int] NOT NULL DEFAULT 0; ALTER TABLE bankfxrm add [工作流程] [varchar](6) NOT NULL DEFAULT ''; ALTER TABLE bankfxrm add [審核人員] [nvarchar](20) NOT NULL DEFAULT ''; ALTER TABLE bankfxrm add [審核日期] [varchar](10) NOT NULL DEFAULT ''; ALTER TABLE bankfxrm add [已審核] [char](1) NOT NULL DEFAULT ''; ALTER TABLE spbank add [建檔日期] [char](10) NOT NULL DEFAULT ''; ALTER TABLE spbank add [聯絡單id] [int] NOT NULL DEFAULT 0; ALTER TABLE spbank add [工作流程] [varchar](6) NOT NULL DEFAULT ''; ALTER TABLE spbank add [審核人員] [nvarchar](20) NOT NULL DEFAULT ''; ALTER TABLE spbank add [審核日期] [varchar](10) NOT NULL DEFAULT ''; ALTER TABLE spbank add [已審核] [char](1) NOT NULL DEFAULT '';對使用者的影響
- 票據相關 7 個程式(銀行帳戶、應付/應收票據、預估資金表、銀行日報主檔、廠商帳戶表)開始支援工作流程簽核欄位(DB 端已備齊;UI 是否暴露簽核 UI 依 Source/ViewModel 設定)
bank表(公司銀行帳戶)多了「票據號碼」欄位
- 銀行日報明細的 view 名稱統一為
vw_bankfxrd,相關報表 / browse 設定一致化
-
05-22 bankflow-baseline-chart ▸
2026-05-22 — Bankflow 全部銀行帳戶加結餘趨勢圖 + BaseLine 警示
摘要
Bankflow(資金預估表 O110)的「全部銀行帳戶」分頁加上結餘金額趨勢圖與 BaseLine 警示功能:原本只有純表格,現在右側顯示折線圖、警示線會跟著查詢條件的 BaseLine 即時移動,結餘跌破 BaseLine 的列在 grid 上會自動顯紅底。
新增
- BankflowCondition 的 BaseLine 欄位實際生效:之前只是 UI 上一個 TextBox 沒接後續,現在輸入後會:
1. 第三個 tab 折線圖上畫一條紅色水平警示線
2. 同 tab grid 的列在
結餘金額 < BaseLine時上半透明紅底- 全部銀行帳戶 tab 折線圖:X = 日期、Y = 結餘金額,資料來源
Bankflow03
TsControl/Converter/BankflowBaseLineRowBackgroundConverter.cs:MultiValueConverter,輸入[結餘金額, BaseLine],回 Brush
變更
TsERP/O.CAandCL/Bankflow.xaml
- 加
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"-
TsView.Resources加入 converter 引用- 第三個 tab Grid 加入
RadCartesianChart(Grid.Column=1)-
bankflow03DataGrid加RowStyle(MultiBinding結餘金額+View.DataContext.QueryConditionViewModel.BaseLine)ViewModel/O.Check/BankflowViewModel.cs拿掉舊 LiveCharts 殘留:
- 刪除
UIUpdateoverride、CreateChartmethod- 刪除
Labels、YFormatter屬性、註解掉的SeriesCollection區塊- 對應拿掉
System.Windows.Media、System.Windows.Threading、System.Text、System.Threading.Tasksusing刪除
無外部 API / DB 欄位 / 設定值刪除。
Breaking Changes
無。
BaseLine仍維持在BankflowConditionViewModel的 local field(沒搬進BankflowQueryParameters),未影響任何序列化 / 設定檔。Migration Notes
無 DB / 設定遷移需要。重新 build TsERP 即生效。
相關
- [[ADR 0021 — Bankflow 全部銀行帳戶 tab 折線圖採用 Telerik RadCartesianChart]](../decisions/0021-bankflow-chart-telerik.md)
-
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-21 procno-bom-m-cols ▸
2026-05-21 — procno / bom_m schema 更新 + 人事資料模板種子
摘要
兩組獨立 DB 變動:
1. Schema —
procno(工程代號表)新增 4 個欄位以支援會簽流程與建檔日追蹤;bom_m(BOM 主檔)新增建檔日期欄位。同步更新vw_wkcent、vw_procno、vw_bom_m三個 view 把新欄位納入查詢結果。2. 種子資料 — 新增人事資料模板
P1(人事明細資料),含 23 筆datatpld欄位定義 + 1 筆datatplm主檔,分 4 個子群組(緊急聯絡人 / 眷屬資料 / 學歷 / 任職經歷)。變更
Schema
procno:新增 4 個欄位
-
建檔日期char(10)NOT NULL DEFAULT ''-
聯絡單idintNOT NULL DEFAULT 0-
工作流程varchar(6)NOT NULL DEFAULT ''-
已審核char(1)NOT NULL DEFAULT ''bom_m:新增 1 個欄位
-
建檔日期char(10)NOT NULL DEFAULT ''vw_wkcent:SELECT 補入聯絡單id、工作流程、審核人員、審核日期、已審核、建檔日期
vw_procno:SELECT 補入建檔日期、聯絡單id、工作流程、已審核
vw_bom_m:SELECT 補入建檔日期
種子資料
datatplm/datatpld:新增人事資料模板P1(人事明細資料)
-
P1_1_B_緊急聯絡人:4 欄位(緊急聯絡人 / 緊急關係 / 緊急電話 / 緊急地址)-
P1_2_B_眷屬資料:6 欄位(眷屬稱謂 / 眷屬姓名 / 眷屬出生 / 眷屬職業 / 身份證號 / 眷屬關係)-
P1_3_B_學歷:6 欄位(學校名稱 / 院系科別 / 修業年月起 / 修業年月迄 / 是否畢業 / 學位)-
P1_4_B_任職經歷:7 欄位(原任職公司 / 原部門 / 原任職日期 / 原離職日期 / 原待遇 / 原工作內容 / 原離職原因)Migration Notes
對每個目標 DB 依序執行以下兩段 SQL。
1. 加欄位
ALTER TABLE procno ADD [建檔日期] [char](10) NOT NULL DEFAULT ''; ALTER TABLE procno ADD [聯絡單id] [int] NOT NULL DEFAULT 0; ALTER TABLE procno ADD [工作流程] [varchar](6) NOT NULL DEFAULT ''; ALTER TABLE procno ADD [已審核] [char](1) NOT NULL DEFAULT ''; ALTER TABLE bom_m ADD [建檔日期] [char](10) NOT NULL DEFAULT ''; GO2. 更新 view 定義
ALTER VIEW [dbo].[vw_wkcent] AS SELECT dbo.wkcent.中心群組, dbo.wkcent.中心代號, dbo.wkcent.中心說明, dbo.wkcent.報價費率, dbo.wkcent.產能, dbo.wkcent.人工費率, dbo.wkcent.間接費比例, dbo.wkcent.每週天數, dbo.wkcent.部門代號, dbo.wkcent.輸入日期, dbo.wkcent.輸入人員, dbo.wkcent.輸入地點, dbo.wkcent.增刪修, dbo.wkcent.選擇, dbo.wkcent.管制碼, dbo.wkcent.srvdbid, dbo.wkcent.pkid, dbo.wkcent.logid, dbo.wkcent.linkid, ISNULL(dbo.vwseek_grpm1.說明, N'') AS 說明, ISNULL(dbo.dept.部門簡稱, N'') AS 部門簡稱, dbo.wkcent.聯絡單id, dbo.wkcent.工作流程, dbo.wkcent.審核人員, dbo.wkcent.審核日期, dbo.wkcent.已審核, dbo.wkcent.建檔日期 FROM dbo.wkcent LEFT OUTER JOIN dbo.vwseek_grpm1 ON dbo.wkcent.中心群組 = dbo.vwseek_grpm1.群組代號 LEFT OUTER JOIN dbo.dept ON dbo.wkcent.部門代號 = dbo.dept.部門代號; GOALTER VIEW [dbo].[vw_procno] AS SELECT dbo.procno.工程代號, dbo.procno.工程簡稱, dbo.procno.報價簡稱, dbo.procno.工程說明, dbo.procno.中心群組, dbo.procno.重要度, dbo.procno.重要項目, dbo.procno.圖檔名稱, dbo.procno.製造規格, dbo.procno.檢驗規格, dbo.procno.審核人員, dbo.procno.審核日期, dbo.procno.損耗率, dbo.procno.輸入日期, dbo.procno.輸入人員, dbo.procno.輸入地點, dbo.procno.增刪修, dbo.procno.選擇, dbo.procno.管制碼, dbo.procno.srvdbid, dbo.procno.pkid, dbo.procno.logid, dbo.procno.linkid, dbo.vwseek_grpm1.說明, dbo.procno.建檔日期, dbo.procno.聯絡單id, dbo.procno.工作流程, dbo.procno.已審核 FROM dbo.procno LEFT OUTER JOIN dbo.vwseek_grpm1 ON dbo.procno.中心群組 = dbo.vwseek_grpm1.群組代號; GOALTER VIEW [dbo].[vw_bom_m] AS SELECT dbo.bom_m.組件編號, dbo.vwseek_invent.品名, dbo.vwseek_invent.品別, dbo.vwseek_invent.停用說明, dbo.vwseek_invent.設變編號, dbo.vwseek_invent.設變資料, dbo.bom_m.機種別, dbo.bom_m.銷售地區, dbo.bom_m.客戶, dbo.bom_m.生產轉入倉, dbo.bom_m.流程樣板號, dbo.bom_m.聯絡單id, dbo.bom_m.工作流程, dbo.bom_m.審核人員, dbo.bom_m.審核日期, dbo.bom_m.已審核, dbo.bom_m.輸入日期, dbo.bom_m.輸入人員, dbo.bom_m.輸入地點, dbo.bom_m.增刪修, dbo.bom_m.選擇, dbo.bom_m.管制碼, dbo.bom_m.srvdbid, dbo.bom_m.pkid, dbo.bom_m.logid, dbo.bom_m.linkid, dbo.vwseek_invent.零件編號, dbo.bom_m.建檔日期 FROM dbo.bom_m LEFT OUTER JOIN dbo.vwseek_invent ON dbo.bom_m.組件編號 = dbo.vwseek_invent.品號; GO執行順序:先加欄位再更新 view,避免 view 引用未存在欄位失敗。
3. 人事資料模板種子(datatpld + datatplm)
跑過會在
datatplm留一筆P1主檔、datatpld留 23 筆欄位定義。若目標 DB 已有相同 pkid 會衝突,視情況決定是否先 DELETE。INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'1 ', N'原任職公司 ', N'C', 30, 0, N'服務機關名稱', N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_4_B_任職經歷', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 1, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'2 ', N'原部門 ', N'C', 30, 0, N'部門/職稱', N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_4_B_任職經歷', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 2, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'3 ', N'原任職日期 ', N'C', 10, 0, N'到職日', N'9999/99/99', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_4_B_任職經歷', CAST(N'2019-04-18T17:18:41.633' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 3, 215195, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'4 ', N'原離職日期 ', N'C', 10, 0, N'離職日', N'9999/99/99', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_4_B_任職經歷', CAST(N'2019-04-18T17:18:41.633' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 4, 215195, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'5 ', N'原待遇 ', N'N', 18, 6, N'薪資(月)', N'999,999,999', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_4_B_任職經歷', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 5, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'6 ', N'原工作內容 ', N'C', 80, 0, N'主要工作', N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_4_B_任職經歷', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 6, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'7 ', N'原離職原因 ', N'C', 80, 0, N'離職原因', N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_4_B_任職經歷', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 7, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'8 ', N'學校名稱 ', N'C', 30, 0, N'學校名稱', N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_3_B_學歷', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 8, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'9 ', N'院系科別 ', N'C', 30, 0, N'院系科別', N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_3_B_學歷', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 9, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'10 ', N'修業年月起 ', N'C', 7, 0, N'修業起始年月', N'9999/99', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_3_B_學歷', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 10, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'11 ', N'修業年月迄 ', N'C', 7, 0, N'修業結束年月', N'9999/99', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_3_B_學歷', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 11, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'12 ', N'是否畢業 ', N'C', 1, 0, N'畢/肄', N'Y', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_3_B_學歷', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 12, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'13 ', N'學位 ', N'C', 30, 0, N'學位', N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_3_B_學歷', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 13, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'14 ', N'眷屬稱謂 ', N'C', 10, 0, N'稱謂', N'XXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_2_B_眷屬資料', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 14, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'15 ', N'眷屬姓名 ', N'C', 10, 0, N'姓名', N'XXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_2_B_眷屬資料', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 15, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'16 ', N'眷屬出生 ', N'C', 10, 0, N'出生日期', N'9999/99/99', N'CHKDATE', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_2_B_眷屬資料', CAST(N'2014-06-09T00:04:00.727' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 16, 64740, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'17 ', N'眷屬職業 ', N'C', 20, 0, N'職業', N'XXXXXXXXXXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_2_B_眷屬資料', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 17, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'18 ', N'緊急聯絡人 ', N'C', 20, 0, N'姓名', N'XXXXXXXXXXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_1_B_緊急聯絡人', CAST(N'2022-10-13T17:20:14.240' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 18, 526961, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'19 ', N'緊急關係 ', N'C', 10, 0, N'關係', N'XXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_1_B_緊急聯絡人', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 19, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'20 ', N'緊急電話 ', N'C', 20, 0, N'電話', N'XXXXXXXXXXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_1_B_緊急聯絡人', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 20, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'21 ', N'緊急地址 ', N'C', 80, 0, N'地址', N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_1_B_緊急聯絡人', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 21, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'48 ', N'身份證號 ', N'C', 14, 0, N'身份證號', N'XXXXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_2_B_眷屬資料', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 55, 64734, 3); GO INSERT [dbo].[datatpld] ([序號], [欄位], [資料型態], [長度], [小數位數], [標題], [輸入遮罩], [valid方法], [公式], [參照資料], [輸入方式], [輸入內容], [允許輸入], [搜尋方式], [權限], [備註], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'49 ', N'眷屬關係 ', N'C', 10, 0, N'眷屬關係', N'XXXXXXXXXX', N'', N'', N'', N'SQLTEXT ', N'', N' ', N'', N'', N'P1_2_B_眷屬資料', CAST(N'2014-06-08T23:59:14.177' AS DateTime), N'Gary', N'CMSA-SERVER # cmsa', N' ', N' ', 0, 1, 56, 64734, 3); GO INSERT [dbo].[datatplm] ([資料模板號], [說明], [輸入日期], [輸入人員], [輸入地點], [增刪修], [選擇], [管制碼], [srvdbid], [pkid], [logid], [linkid]) VALUES (N'P1 ', N'人事明細資料', CAST(N'2012-10-04T15:32:02.280' AS DateTime), N'令狐沖', N'WXWG101234 # wg101', N' ', N' ', 0, 1, 3, 2109, 0); GO執行順序:schema → view → 種子資料皆獨立,可分批執行。種子資料若已存在會 PK 衝突,需先評估是否清除舊資料。
-
05-21 perdata-add-multiple-rows ▸
2026-05-21 — Perdata 右鍵「新增數筆」支援動態 DataTable detail
摘要
人事資料(PERDATA)的三個動態欄位 grid(眷屬與聯絡人 / 學歷 / 任職經歷)現在支援 RadGridView 右鍵選單的「新增數筆」。每按一次補 10 列空白,序號接著當前最大值往下排。
變更
ViewModel/R.HumanResource/PerdataViewModel.cs
- 新增
ContactsAdder/EducationAdder/WorkExperienceAdder三個IDataGridAdd屬性- 新增私有巢狀類別
CallbackGridAdd,把Action包成IDataGridAdd- 三個 adder 在建構子內建立,呼叫既有的
AddLineNo(dt)對Contacts/Education/WorkExperienceDataTable 加列TsERP/R.HumanResource/Perdata.xaml
-
DgContact、DgEducation、DgWorkExperience各加DataGridAdd="{Binding XxxAdder}"背景
TsRadGridView.AddNewRecordsExecute()有兩條路:DataGridAdd != null → DataGridAdd.AddItem() ← 客製化 detail 否則 → AdeViewModel.AddNewRecord(DataManager, ...) ← ObservableCollection-based detailPerdata 的 detail 不是
DataManager<TSource>+ObservableCollection,而是用TsTemplateDataTable從datatpld動態產欄位後綁DataTable,沒有DataManager可用,必須走DataGridAdd路徑。DgOther是固定的List<其他資料Poco>(沒有「新增更多筆」的概念),不接此功能。不影響
- 不影響原本進入新增 / 編輯模式時預先塞 10 列空白的行為(
AfterAddNew/EditBll_OnAfterEdit)
- 不影響
SaveBll_OnBeforeAllowSave把四個 detail 包成TemplateDynamicDataGridPoco的儲存流程
- 不影響其他模組的
TsRadGridView右鍵選單行為
-
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):產生