Day 28:報表模組接上權限與租戶隔離,完工 結尾留下一句話,全系列整合驗收,跑一次從新增商品到產生報表的完整流程,是接下來要處理的內容。今天要處理的正是這件事。
昨天講完最後一句話的時候,其實藏著一個值得慢下來想一想的事實。商品、訂單、客戶、庫存、報表,五個模組今天總算全部到齊,但這五個模組是分開、分批建置出來的,商品在 Day 03、Day 04 就有了雛形,訂單跟客戶在 Day 09 到 Day 12 陸續串起關聯,報表則是最近三天才從零開始生出來。從系列第一天到昨天為止,從來沒有人真正從頭到尾走過一次完整的業務流程,從新增一筆商品開始,一路做到最後看到報表反映出這筆商品的銷售成果。
這句話聽起來像是隨口的提醒,實際上指出一個容易被忽略的落差。過去二十八天,每一項機制的驗證方式,幾乎都是針對那項機制本身量身設計的情境。驗證多租戶隔離的時候,用的是專門為了測試據點區隔而登入的兩組帳號,操作的重點是看訂單列表,不見得同時走過新增商品、建立訂單這整段流程。驗證 Widget 排版跟權限呼應的時候,關注的是首頁這一個畫面,不會特別去確認首頁上顯示的數字,是不是真的來自剛剛才發生的一筆新業務。每一項機制各自過關,這件事沒有問題,問題在於各自過關這件事,沒辦法保證機制跟機制銜接的地方也一樣順暢。新增商品之後,訂單表單裡的商品選項有沒有立刻反映這筆新商品,這種銜接點正是分開驗證時完全不會被觸及的死角。
這正是端到端驗收存在的理由。驗收一棟房子,不會只是分別檢查水電、結構、消防各自過關就算數,還得實際住進去生活一天,看看動線通不通順,晚上開燈會不會跳電,浴室排水順不順暢。單項檢查告訴你每個系統本身沒有問題,只有真正住進去走一遍日常動線,才知道這些系統擺在一起運作時,會不會在某個轉角互相絆到。
也許有人會覺得,既然每一項機制都各自驗證過,疊加起來理所當然沒有問題,不需要再花一整天特別驗證。這個直覺低估了整合本身的價值。各自驗證只能確保單一機制在孤立情境下正確,沒辦法確保這些機制在一次連續、跨模組的真實操作流程裡,每一個交接點都順暢無礙。至於整合驗收要驗到什麼程度才算完整,務實的做法不是窮舉所有可能的操作路徑組合,訂單明細裡商品的交叉組合、三個角色乘上兩個據點的所有排列,真要窮舉,工作量會遠遠超過今天一天能處理的範圍。更務實的做法,是選擇一條最貼近示範情境業務型態、涵蓋最多既定錨點的代表性路徑,走完這條路徑,足以建立起對整套系統的信心,不代表窮盡了所有可能發生的情境。這一點會在文章後段回頭誠實補充。
今天的任務,就定調在這條具代表性的路徑上,用一個貼近示範情境業務型態的具體案例,從新增一筆全新商品開始,走完整個業務流程,直到這筆商品的銷售成果出現在報表上,途中依序觸發系列建立的多項機制,用這一次連貫的操作,驗證整套系統疊加運作時依然正確。
案例先具體定下來,接下來全文只用這一個案例走到底,不中途更換。這間寵物用品批發商最近談成一款新進的貓抓板商品,命名為「北歐風貓抓板」,準備第一次上架進系統。
用倉管角色的帳號登入後台,打開商品 Resource 的新增頁面。走過的表單欄位,跟 Day 04:表單欄位全解析,從文字輸入到日期選擇器 定案的欄位型別、Day 13:表單驗證不只是必填,跨欄位規則怎麼寫 補齊的驗證規則完全一致,商品名稱填入「北歐風貓抓板」,分類選擇既有的貓抓板類別,價格與庫存數量各自填入合理的數字,圖片欄位上傳一張商品照片。送出表單,必填驗證與數值範圍驗證正常攔下任何缺漏或不合理的輸入,一切正常的話,這筆商品順利存進資料庫,商品列表裡第一次出現「北歐風貓抓板」這一行。
// ProductResource 表單欄位,沿用 Day 04、Day 13 定案內容,不重複列出完整定義
TextInput::make('name')
->label('商品名稱')
->required()
->maxLength(255),
Select::make('category')
->label('分類')
->options([
'food' => '寵物飼料',
'toy' => '寵物玩具',
'cleaning' => '清潔用品',
'scratching_board' => '貓抓板',
])
->required(),
TextInput::make('price')
->label('價格')
->numeric()
->minValue(0)
->required(),
這一步同時要確認另一件事,這個新增動作有沒有依循 Day 15:接上 Laravel 既有的 Policy,誰能看見這個操作按鈕 定案的 Policy 整合、Day 16:設計一份貼近真實團隊的角色權限矩陣 建立的角色矩陣。倉管角色在矩陣裡本來就具備商品的建立權限,這一步理所當然順利放行。換個角度想,如果今天登入的是財務角色,新增商品這顆按鈕根本不會出現在畫面上,這是 Policy 整合在背後自動判斷的結果,不需要今天重新驗證財務角色被擋下的細節,Day 15、Day 16 早就把這件事講清楚,這裡只需要確認正確的角色確實放行、動作確實成功。
第一步完成,商品模組跟權限地基,在這條路徑上第一次被實際串起來檢驗。
商品有了,接下來讓它第一次出現在一張真實的訂單裡。維持同一個倉管角色略嫌不合理,改用業務角色的帳號登入,這個角色在矩陣裡具備建立訂單的權限,也貼近實際業務情境,通常是業務跟客戶談成一筆訂單,而不是倉管。
打開訂單 Resource,新增一筆訂單,選擇既有客戶「旺旺寵物用品行」,填入訂單編號與日期,送出後進入訂單編輯頁面。頁面下方接著出現 Day 11:Relation Manager,訂單明細這種巢狀資料怎麼管理 定案的訂單明細 Relation Manager,點擊新增明細,打開這個獨立子介面的表單。
// OrderItemsRelationManager 表單,沿用 Day 11 定案內容
Select::make('product_id')
->label('商品')
->relationship('product', 'name')
->searchable()
->required(),
TextInput::make('quantity')
->label('數量')
->numeric()
->minValue(1)
->default(1)
->required(),
在這個下拉選單裡打「北歐風」三個字搜尋,這正是整條路徑裡第一個真正的驗收關卡。這個 Select 欄位背後用的是 Day 09:表單裡選關聯資料,下拉選單背後在做什麼 拆解過的 relationship('product', 'name') 機制,選項不是寫死在程式碼裡的固定清單,而是即時查詢商品資料表得到的結果。剛才在第一步新增的「北歐風貓抓板」,此刻能不能立刻出現在這份搜尋結果裡,不是理所當然的事,而是需要今天親眼驗證的銜接點,這正是各自驗證時完全不會觸及的死角,新資料誕生後,是否立刻在下一個模組裡可用。
搜尋結果裡「北歐風貓抓板」如預期出現,選取它,填入數量三,送出。這筆明細立刻出現在 Relation Manager 的列表裡,訂單編輯頁完全不需要重新整理或重新送出外層表單,這正是 Day 11 強調過的獨立子介面特性,明細的增刪不受外層訂單表單送出流程影響。
這筆新訂單還有一件事要確認,它是否依循 Day 17:多租戶隔離,讓每個經銷據點只看到自己的資料 定案的多租戶隔離,正確歸屬於操作者所屬的經銷據點。今天登入的業務帳號屬於台北據點,訂單建立當下,BranchScope 在背後自動把這筆訂單的 branch_id 對應到台北,不需要在新增訂單的表單裡額外出現一個選擇據點的欄位,這正是 Day 17 選擇用 Global Scope 而不是逐處手動加條件的價值所在,新增一筆資料的地方,不需要重新記得處理隔離邏輯。
第二步完成,關聯欄位機制、Relation Manager、多租戶隔離,三個既定錨點在同一次操作裡依序被觸發,且彼此銜接無礙。
一筆新商品、一張新訂單都已經存在,接下來換上不同身分的帳號,確認權限矩陣跟多租戶隔離,在這兩筆剛誕生的真實資料上依然正確運作,而不是只在過去測試專用的假資料上正確。
先切換據點。登出剛才台北據點的業務帳號,換上高雄據點的業務帳號登入,打開訂單列表跟客戶列表。畫面上完全看不到剛才新增的那筆訂單,也看不到「旺旺寵物用品行」這筆客戶資料,兩份列表只顯示高雄據點自己原本就有的資料。這確認了一件事,多租戶隔離不是只在過去反覆測試過的既有資料上生效,而是連這一秒鐘才剛誕生的全新資料,也立刻被正確納入隔離範圍,BranchScope 不需要針對新資料額外做任何處理。
再切換角色。登出高雄業務帳號,換回台北據點,但這次用財務角色登入,打開剛才那張訂單。財務角色在矩陣裡看得到訂單內容,包含訂單明細裡的「北歐風貓抓板」與數量三,但編輯按鈕不會出現,這正是 Day 16 矩陣裡財務角色的既定設計,看得到金額相關的訂單資訊,但不具備編輯訂單的權限。換成倉管角色登入,情況又不一樣,倉管看得到「北歐風貓抓板」這筆商品本身的庫存資料,但打開訂單列表時,能操作的範圍明顯比業務跟財務窄,這同樣是矩陣既定的職責劃分,不是今天臨時調整出來的結果。
最後打開後台首頁,確認 Day 19:第一個 Widget,把訂單數字擺上首頁、Day 20:圖表 Widget,讓庫存與訂單趨勢一眼看懂、Day 21:誰登入看到誰的儀表板,Widget 排版與權限呼應 建立的統計數字與圖表 Widget,正確反映出剛才新增的這筆訂單。用台北據點的業務帳號登入首頁,本月訂單數比一小時前多了一筆,訂單趨勢折線圖在今天這個日期的位置也多墊高了一點,數字跟線條的變化,都對應著剛才那張真實建立的訂單,不是一份停留在過去某個時間點、跟眼前操作無關的靜態畫面。倉管帳號登入首頁看到的則是庫存分佈圖表,因為 canView() 判斷的結果,訂單相關的 Widget 對倉管角色不會顯示,這跟 Day 21 定案的角色呼應邏輯完全一致。
第三步完成,角色矩陣、多租戶隔離、Widget 顯示與排版邏輯,三層機制在同一批真實資料上,依序被不同身分的帳號驗證過一輪,沒有一處掉鏈子。
前面三步走完,這條業務鏈路只剩最後一段。換回台北據點的業務或財務帳號,打開報表模組,這是 Day 26:報表模組從零開始,從需求到 Resource 骨架 到 Day 28:報表模組接上權限與租戶隔離,完工 這三天才剛剛建置完成的最後一塊拼圖。
點開商品分類銷售量圖表,貓抓板這個分類的長條,比操作之前多墊高了三個單位的銷售數量,正好對應剛才那筆訂單裡填入的數量。點開銷售趨勢圖表,今天這個日期對應的金額也隨之增加,增加的幅度正好等於「北歐風貓抓板」的價格乘上三。這個結果驗證了一件重要的事,報表模組彙整查詢的對象,是即時查詢得到的最新業務資料,不是停留在某個時間點、需要手動重新產生的快照,剛才在幾分鐘前才發生的操作,此刻已經反映在報表數字裡。
換上高雄據點的帳號檢視同一份報表,貓抓板分類的銷售數量跟剛才台北據點看到的結果不一樣,高雄的數字不會因為台北多了這筆訂單而跟著變動,這呼應 Day 28 完成的報表權限整合,報表模組跟其他模組一樣,依登入者所屬據點呈現各自範圍內的資料。
走到這一步,代表從新增商品到產生報表這條完整的業務鏈路,途中觸發的每一層機制,關聯欄位、驗證規則、Policy 整合、角色矩陣、Relation Manager、多租戶隔離、Widget、報表彙整查詢,全部正確銜接,沒有任何一個環節掉鏈子。一筆全新商品,從誕生到出現在訂單裡,到不同角色與據點的人看到各自該看到的範圍,到最後反映在報表的銷售數據上,整條路徑走下來,系統的表現跟過去二十八天各自建立的預期完全一致。

前面四步走完,讀者可能會有一種「全部都測過了」的錯覺,這裡得誠實澄清一下,避免這個錯覺留到明天。
今天走過的是一條具代表性、涵蓋最多既定錨點的路徑,不代表窮舉了所有可能的操作組合。訂單明細裡可以同時放進多個不同商品的交叉組合,今天的案例只放了一個商品;三個角色乘上兩個據點,理論上有六種身分組合,今天實際切換驗證的只是其中幾種具代表性的排列,沒有把六種組合逐一走過一遍;商品分類、訂單狀態、報表篩選條件的排列組合,實際可能發生的情境數量,遠比今天示範的更多。今天驗證的是這套系統在一條貼近真實業務型態的路徑上正確運作,不是宣告這套系統窮盡所有情境都不會出錯。
這種端到端驗收,如果未來要常態化執行,一次一次靠人工重新操作一遍,顯然不是長久的做法,人力成本高,也容易因為疏漏而漏測某個環節。這件事理應交給 Day 24:幫 Panel 寫測試,Testable 方法組合怎麼用 已經建立的 Testable 自動化測試機制,把今天走過的這條路徑,寫成一條或多條端到端測試情境,讓它可以被重複、穩定地執行,不需要每次都靠人工重新操作一遍。具體要怎麼把今天這四個步驟拆解成測試案例,留待後續視需要處理,這裡不展開任何具體實作方向。
今天是第八階段「實戰整合收尾」的第四篇,也是系列五個階段收尾篇之一,適合在這裡完整回顧這四天走過的路徑,也是整個系列目前累積最厚重的一次階段回顧。
Day 26:報表模組從零開始,從需求到 Resource 骨架 沒有急著動手寫程式,先把管理者實際想從報表裡看到什麼問題具體列出來,訂單總數與總金額、哪些商品賣得最好、哪些商品庫存偏低,再依循系列已經建立的流程,建立彙整查詢類別跟報表 Resource 骨架,驗證這套建置流程的可複製性。Day 27:報表模組加上圖表與篩選條件 接手把骨架轉化成有圖表、有篩選條件的視覺化報表,銷售趨勢圖表沿用圖表 Widget 的既有做法,時間區間篩選沿用自訂 Filter 的既有做法,全部直接複製系列前面已經走過的路,沒有一處是重新發明出來的新技巧。Day 28:報表模組接上權限與租戶隔離,完工 把 Policy 整合、角色權限矩陣、多租戶隔離接上報表 Resource,讓報表跟商品、訂單這些模組一樣,具備完整的身分判斷與資料邊界,也在那一天宣告商品、訂單、客戶、庫存、報表五個模組首次全部到齊。今天用一次端到端的完整操作,驗證這五個模組加上系列建立的所有機制,疊加運作時依然正確,把「到齊」進一步證明成「真正能用」。
把這個階段前後對照一下,落差相當具體。階段開始之前,商品、訂單、客戶、庫存四個模組運作良好,但報表模組完全空白,示範情境裡從第一天就定案的五個模組,從來沒有被當成一套完整系統驗證過。階段結束的今天,五個模組全部到齊,而且經過一次真實業務操作的端到端驗證,確認彼此銜接無礙,這是全系列第一次,這套後台以「一套完整系統」的姿態被檢視,而不是「一堆各自獨立驗證過的功能」的集合。
這正是系列一直在講的那條主線,陽春後台逐步加深,三十天以來最終該抵達的樣貌。第一天做出來的商品 Resource,只是一套幾分鐘生出來的陽春 CRUD 介面,走到今天,同一套系統已經是一套從骨架開始、逐層加深功能與嚴謹度、最終被完整驗證過的正式系統。這條主線走到這裡,不再只是紙上談兵的敘事鉤子,而是今天親手操作一遍就能感受到的具體現實。
一筆全新商品,從誕生到出現在訂單裡,到不同角色與據點的人看到各自該看到的範圍,到最後反映在報表的銷售數據上,這一整條業務鏈路,途中觸發的每一層機制都正確銜接,沒有掉鏈子。五個模組今天不再是分開驗證過的一堆功能,而是一套經過端到端驗證的完整系統。
這套後台走到今天,已經具備一個真正能上線的系統該有的完整樣貌,功能、關聯、驗證、權限、多租戶、儀表板、效能、測試、部署設定、報表,一應俱全,且經過端到端驗證。回頭看三十天前 Day 1 提出的那個問題,Filament 是不是一個值得信任的選擇,今天這次驗收正是回答這個問題最直接的具體證據。
但拿到一份具體證據,不代表答案已經完整。這個答案是否真的成立,還需要誠實檢視這條路上做過的每一個取捨,以及這套框架本身有哪些限制、哪些情境其實不適合交給它處理。完賽總結,Filament 的限制與不適合的情境,是接下來要處理的內容。