🔗 線上 Demo:https://atayallin.github.io/restaurant-inventory-system-React/(免安裝,打開即可操作)
一套為餐飲業打造的進銷存管理系統,整合 AI 自動化功能,協助店家掌握庫存、銷售與營運狀況。本專案為求職作品集,展示前端工程能力與 AI 自動化整合經驗。
傳統餐飲業進銷存管理仰賴人工記帳與經驗判斷,容易發生庫存短缺、食材過期、營運數據不透明等問題。本系統透過網頁介面數位化管理商品、食材、採購與銷售流程,並串接 AI 服務,提供補貨建議與自然語言查詢功能,讓店家用口語問題就能快速掌握營運狀況。
GitHub:AtayalLin/restaurant-inventory-system-React
| 分類 | 技術 |
|---|---|
| 前端框架 | React 19 + TypeScript + Vite |
| 資料狀態管理 | TanStack Query v5 |
| 樣式 | Tailwind CSS v4 |
| 後端 | Spring Boot 3.5 + MySQL API(另一個 repo);開發期也可用 json-server,線上 Demo 用 MSW |
| 測試 | Vitest+MSW(單元/整合)、Playwright(端對端) |
| CI/CD | GitHub Actions(lint、測試、型別檢查、建置、自動部署 GitHub Pages) |
| AI 自動化 | n8n(Docker 自架)+ NVIDIA NIM |
| 圖表 | Recharts |
- 商品管理、食材庫存管理、採購進貨、銷售管理四大模組
- Zod 表單驗證、Mock/真實 API 雙模式切換
- 今日/本週/本月營收統計、平均客單價
- 低庫存清單、即期食材警告
- 商品毛利率排行、近 7 天銷售趨勢圖
- 30 秒自動輪詢更新
- 串接 NVIDIA NIM,依商品資訊自動生成行銷描述文案(Streaming API)
- 依低庫存食材清單,透過 n8n workflow 呼叫 AI 生成補貨建議
- 架構:
Webhook → Code(組 prompt) → HTTP Request(呼叫 NVIDIA NIM) → Respond to Webhook
- 使用者可用自然語言提問(例如「本週營收多少」),系統回傳 AI 組織好的口語化回答
- 架構決策:後端為 json-server(非 SQL),不適合走業界常見的 NL2SQL 路線,改採「意圖分類 + 語意層」架構(概念類似 Snowflake Cortex Analyst)——AI 只負責「判斷問題屬於哪種預定義意圖」與「將運算結果組織成自然語言」,實際數字運算交由寫死的 Code 節點邏輯處理,避免 AI 算錯數字
- 目前支援意圖:
REVENUE_COMPARISON:本週/本月營收,與上期比較(含成長率計算)TOP_SELLING_PRODUCTS:銷售排行榜(規劃中)GENERAL_INVENTORY_QA:其他進銷存相關問答(規劃中)UNKNOWN:無法歸類的問題,回覆固定引導文字
- n8n workflow 節點:
Webhook → Code(意圖判斷prompt) → HTTP Request(AI判斷意圖) → Code(解析意圖+撈資料運算+組回答prompt) → HTTP Request(AI生成回答) → Respond to Webhook
關鍵技術決策:n8n 節點間傳遞資料時,複雜的內嵌 {{ }} 表達式容易解析失敗(Fixed/Expression 模式切換不穩定),改用獨立 Code 節點先組好純文字 prompt,再由 HTTP Request 節點以 {{ JSON.stringify({...}) }} 整包表達式送出,大幅提升穩定性。
第一次啟動前:複製環境變數範本,填入自己的 NVIDIA API 金鑰
cp .env.example .env
# 編輯 .env,填入 NVIDIA_API_KEY=nvapi-...🔒 金鑰不會進入前端:
NVIDIA_API_KEY沒有VITE_前綴,只由vite.config.ts的 proxy 在開發伺服器端加到請求標頭,前端程式碼與npm run build的打包檔都不會出現金鑰。 API 與 n8n 網址統一在src/lib/config.ts管理,可用.env覆寫,未來換成 Spring Boot 後端只需修改API_PROXY_TARGET。
需要同時開啟三個終端機:
# 終端機 1:前端開發伺服器
npm run dev # http://localhost:5173
# 終端機 2:json-server mock 後端
npm run server # http://localhost:3001
# 終端機 3:n8n 自動化服務(Docker Desktop 需先手動開啟)
docker start n8n # http://localhost:5678環境注意事項:
- n8n 容器內部呼叫宿主機 json-server,必須使用
http://host.docker.internal:3001,不能用localhost:3001(容器內部的 localhost 指向自己) - n8n 容器為永久容器(未加
--rm),workflow JSON 備份存放於 git 專案目錄外的React-Frontendproject/n8n-workflows/,避免明文金鑰被 commit
線上 Demo 部署在 GitHub Pages,那裡只能放靜態檔案,沒有 json-server、n8n,也不能放 API 金鑰。所以 Demo 版用 MSW(Mock Service Worker) 在瀏覽器裡攔截請求、模擬後端:
- 前端程式碼不用為了 Demo 修改:hooks 照樣呼叫
/api/products、n8n webhook 與/nvidia-api,由 Service Worker 在請求送出前接手回應(src/mocks/handlers.ts)。 - 資料以今天為基準產生:
src/mocks/seed.ts動態產生近 60 天的訂單,任何時候打開,「今日營收」「近 7 天」都有數字。用固定種子的亂數,同一天看到的資料一致。 - 操作會保留:新增商品、出餐扣庫存等變更存在訪客自己瀏覽器的 localStorage,重新整理後仍在;頁首可「重設資料」。
- AI 功能改為範例回應:查詢助理的數字一樣由程式從資料計算(與正式版「程式算數字、AI 只負責講話」的設計相同),補貨建議與菜單文案則用範本產生,並在回答中標示為 Demo 範例。
- 一般版本不含模擬程式碼:只有
vite build --mode demo才會載入src/mocks。
npm run dev:demo # 本機用 Demo 模式開發(不需要 json-server 與 n8n)
npm run build:demo # 建置 Demo 版到 dist/部署:push 到 main 後,.github/workflows/deploy-demo.yml 會自動建置並部署。第一次使用前,到 repo 的 Settings → Pages → Build and deployment → Source 選「GitHub Actions」。
CI:每次 push 與 Pull Request,.github/workflows/ci.yml 會跑 ESLint、單元測試、TypeScript 型別檢查、一般版與 Demo 版建置,以及 Playwright 端對端測試。
npm test # 單元/整合測試(Vitest)
npm run test:watch # 開發時監看模式
npm run test:e2e # 端對端測試(Playwright,會自動建置並啟動 Demo 版)測試直接沿用 Demo 的 MSW 模擬後端(src/test/setup.ts),不需要啟動 json-server,涵蓋:
- 金額計算:營業稅 5% 無條件捨去、總計、成長率(上期為 0 時不顯示百分比)
- 出餐扣庫存:依配方扣減、同食材跨商品合併計算、庫存不足時全部不扣
- 樂觀鎖:採購收貨增加庫存並遞增版本號、扣成負數時拒絕
- 登入:後端登入、json-server 退回本機帳號、請求自動帶 token、401 通知登出、403 不重試
- 角色權限:三種角色的權限矩陣、門市篩選(舊資料歸預設門市)、跨店營收只算期間內已完成訂單
- 叫貨規則:最低訂購量、供應商不符、重複食材、叫貨日提醒、由預估產生採購草稿
- 需求預測:只取過去 4 個同星期的已完成訂單、資料不足時以實際週數平均、已下架商品排除、叫貨量進位
- 查詢助理:意圖判斷、排行榜排序、補貨建議數量
測試固定「現在時間」,結果不會隨日期改變。
端對端測試(e2e/):用 Playwright 開真的瀏覽器操作 Demo 版,驗證使用者實際會走的流程:
- 未登入導向登入頁、錯誤密碼提示、員工看不到也進不去採購頁、總部切換門市與登出
- 店長點餐 → 備餐 → 出餐,確認牛腱肉庫存依配方減少 200g;折扣超過應付金額無法建立
- 總部從明日預估一鍵叫貨、低於最低訂購量被擋下、建立成功、開啟列印版面
CI 另外跑一個 e2e 工作;失敗時會上傳含截圖與操作紀錄的報告。
參考連鎖餐飲「總部統一菜單、中央廚房統一進貨、門市負責銷售」的分工。登入頁提供三個示範帳號(密碼皆為 demo1234),點一下即可體驗不同角色看到的畫面。
| 功能 | 總部 owner |
店長 manager(左營店) |
員工 staff(左營店) |
|---|---|---|---|
| 切換門市/全部門市、跨店營收比較 | ✅ | — | — |
| 菜單商品新增、編輯 | ✅ | 唯讀 | 唯讀 |
| 食材庫存編輯 | ✅ | ✅ | 唯讀 |
| 採購進貨 | ✅ 可操作 | 唯讀 | 無法進入 |
| 點餐、備餐、出餐 | ✅ | ✅ | ✅ |
| 退款 | ✅ | ✅ | — |
- 權限規則集中在
src/lib/permissions.ts(純函式,有單元測試),頁面用useSession().can('...')判斷。 - 路由守衛
RequirePermission:直接輸入網址也會被擋下,不只是隱藏側欄選項。 - JWT 登入:連到 Spring Boot 後端時,登入取得 JWT,所有
/api請求由src/lib/auth.ts的apiFetch自動帶上 token;token 過期(401)自動登出回登入頁。前端權限只負責介面顯示,真正擋下請求的是後端(沒有權限回 403)。 - 連到 json-server(沒有登入 API)時,改用本機示範帳號登入,方便前端單獨開發。
- 出餐、收貨呼叫後端
POST /salesOrders/{id}/complete、/purchaseOrders/{id}/receive,由後端在同一筆交易內完成;連到 json-server(沒有這些端點)時自動改用前端逐步處理(src/lib/orderActions.ts)。 - 銷售訂單屬於門市(
storeId);食材為中央廚房統一庫存,所以「明日預估」用全部門市的銷售來推估。
參考 iCHEF 門市訂貨管理的做法,規則集中在 src/lib/orderRules.ts(純函式,有單元測試):
| 規則 | 處理方式 |
|---|---|
| 低於食材的最低訂購量 | 阻擋送出,欄位下方顯示最低量;後端同樣檢查(422 BELOW_MIN_ORDER),繞過前端也擋得住 |
| 食材不是這家供應商供應 | 選單只列出該供應商的食材;送出時再檢查一次 |
| 同一食材重複多列 | 阻擋,請合併成一列 |
| 今天不是供應商的叫貨日 | 只提醒、不阻擋,並告知下次叫貨日(臨時追加叫貨在實務上很常見) |
- 明日預估 → 一鍵叫貨:儀表板「明日預估」依供應商分組,產生採購單草稿(已補到最低訂購量、帶入單位成本),總部點一下即可建立。
- 列印採購單:
/purchase/:id/print以 A4 版面呈現供應商資訊、品項、合計與簽核欄,可直接列印或存成 PDF。
以 Lighthouse 12(預設的行動裝置模擬)量測線上 Demo 的登入頁,優化前後比較:
| 指標 | 優化前 | 優化後 |
|---|---|---|
| Lighthouse 效能 | 86 | 94 |
| Lighthouse 無障礙 | 94 | 100 |
| First Contentful Paint | 2.8 s | 2.2 s |
| Largest Contentful Paint | 3.4 s | 2.7 s |
| Total Blocking Time | 130 ms | 50 ms |
| 登入頁下載的 JS(Demo 版,含模擬後端) | 1,259 KB | 723 KB |
| 一般版主程式包 | 848 KB | 290 KB |
做法:
- 路由延遲載入:各頁面改用
React.lazy拆成獨立檔案(src/routes/pages.ts),登入頁不再下載儀表板、圖表等用不到的程式。 - 圖表獨立載入:營收趨勢圖用到的 Recharts 約占儀表板程式的九成,改為延遲載入;統計卡先顯示,圖表隨後補上。
- 空閒時預先下載:登入後利用
requestIdleCallback預先下載常用頁面,切換頁面時通常已下載完成,不會看到載入畫面。 - 移除未使用的套件:
mammoth、pdfjs-dist、react-markdown、axios沒有被使用,從依賴中移除。 - 對比度:次要文字色從
#6B7480調為#5E6672,在白底與頁面灰底都達到 WCAG AA(≥ 4.5:1)。
圖例:✅ 已完成 🔄 開發中/待驗證 🔲 待辦
智慧餐飲進銷存管理系統
│
├── 核心進銷存模組 ✅
│ ├── 商品管理
│ ├── 食材庫存
│ ├── 採購進貨
│ └── 銷售管理
│
├── Dashboard 儀表板 ✅
│ ├── 營收統計 / 近7天趨勢
│ ├── 低庫存清單
│ ├── 即期食材警告
│ ├── 明日預估:同星期平均銷量 → 依配方換算食材用量 → 建議叫貨量
│ └── 商品毛利率排行
│
├── AI 自動化功能(n8n + NVIDIA NIM)
│ ├── Agent A:AI 補貨建議 ✅
│ │
│ └── Agent B:智慧查詢助理
│ ├── 架構:Tool-Calling 語意層
│ │ (AI 選工具 → 固定邏輯運算 → AI 組回答)
│ │
│ ├── REVENUE_COMPARISON ✅
│ ├── TOP_SELLING_PRODUCTS 🔄(驗證中)
│ ├── UNKNOWN ✅
│ ├── GENERAL_INVENTORY_QA 🔲
│ │ ├── getInventoryStatus 🔲
│ │ └── getProductSales 🔲
│ │
│ ├── 進階 KPI 工具(業界標準指標)
│ │ ├── getFoodCostPercentage 🔲
│ │ ├── getABCAnalysis 🔲
│ │ └── getInventoryTurnover 🔲(待 stockLogs 資料累積)
│ │
│ └── Tool-Calling 架構遷移 🔲
│ (由 Intent 分類 + if/else 改為治理過的工具選擇)
│
├── 資料完整性 ✅
│ └── stockLogs 寫入邏輯
│ ├── 銷售完成 → 依 recipe 扣減 ingredient.currentStock
│ └── 採購到貨 → 加回 ingredient.currentStock
│
├── 工程品質
│ ├── 共用 UI 元件與設計 token ✅
│ ├── GitHub Actions CI(lint、型別檢查、建置)✅
│ ├── 線上 Demo(MSW + GitHub Pages)✅
│ ├── Vitest 單元/整合測試(已加入 CI)✅
│ ├── Playwright 端對端測試(已加入 CI)✅
│ └── 效能優化:路由延遲載入、Lighthouse 效能 94/無障礙 100 ✅
│
└── 對標業界的下一階段(依序進行)
├── ① Vitest 單元測試並加入 CI ✅
├── ② 需求預測+叫貨建議 ✅
│ (近 4 週同星期平均銷量 → 依配方換算食材用量 → AI 只負責解釋)
├── ③ 多門市切換+角色權限(總部/店長/員工)✅
├── ④ 叫貨規則:最低訂購量、供應商叫貨日、明日預估一鍵叫貨、列印採購單 ✅
└── ⑤ Spring Boot + MySQL 後端、JWT 登入與權限 ✅
開發方向參考台灣大型餐飲集團與餐飲 POS 業者的公開資料,確認專案功能對應真實需求,而不是只做 CRUD 練習。
| 業界需求 | 業界案例 | 本專案對應 |
|---|---|---|
| 成本毛利管理、供需協同 | 王品與鼎新數智建置新一代資訊核心平台,聚焦供需協同、門店營運、成本毛利管理,為多品牌擴張打基礎 | ✅ 配方扣庫存、商品毛利率排行 |
| 總部多門市管理 | iCHEF 總部系統:跨店菜單統一、跨店業績與商品排行、跨店會員,規劃 API 對接 ERP | ✅ ③ 門市切換、跨店營收比較、總部統一菜單、角色權限 |
| 門市叫貨流程 | iCHEF 門市訂貨管理:依供應商規則訂貨、缺貨自動阻擋、產生撿貨出貨單 | ✅ ④ 最低訂購量阻擋(前後端)、叫貨日提醒、依預估一鍵產生採購單、列印採購單 |
| AI 預測客流與銷售 | iCHEF 與 Google 等推出連鎖 AI 助手「春嬌」,整合 POS、會員、庫存資料預測客流與銷售 | ✅ ② 明日預估(同星期平均)+叫貨建議 |
| AI 報表解讀 | iCHEF AI 報表解讀讓每週分析從約 2 小時縮短到 5 分鐘 | ✅ 自然語言查詢助理(程式算數字、AI 組句) |
| AI 中央廚房 | 高雄「大南方新矽谷」AI 中央廚房補助:產線智慧化、數位食譜 | ⚪ 暫不實作,列為延伸方向 |
設計呼應:「春嬌」的使用者回饋指出,整合後的分析比自行把資料丟給語言模型處理更穩定,與本專案 Agent B「數字由程式計算、AI 只負責說明」的設計方向一致。
資料來源: 科技新報-王品新一代資訊核心平台、 CIO Taiwan-iCHEF AI 與春嬌、 工商時報-iCHEF AI 應用成效、 INSIDE-iCHEF 總部系統、 INSIDE-iCHEF 門市訂貨管理、 工商時報-AI 中央廚房補助
| 色彩 | Hex | 用途 |
|---|---|---|
| 主色(深藍) | #1E3A5F |
Sidebar、標題 |
| 強調色 | #1E40AF |
主要按鈕、連結 |
| 成功色 | #16A34A |
庫存正常 Badge |
| 警告色 | #D97706 |
庫存不足 Badge |
| 危險色 | #DC2626 |
刪除按鈕、錯誤訊息 |
| 背景色 | #F8FAFC |
頁面背景 |
這個 workflow 有兩種測試模式,網址不同、使用方式也不同,不要混用。
適合情境:想在 n8n 編輯器裡即時看到每個節點的輸入/輸出資料,方便除錯。
步驟:
- 開啟 workflow,點擊 Webhook 節點
- 點擊橘色按鈕 「Listen for test event」
- 畫面會顯示:Listening for test event Make a POST request to: http://localhost:5678/webhook-test/query-assistant
- 在終端機執行(注意路徑是
webhook-test):
curl -X POST http://localhost:5678/webhook-test/query-assistant \
-H "Content-Type: application/json; charset=utf-8" \
--data-binary @payload.json- 送出後,編輯器會自動跳轉並顯示執行結果,可以逐一點開每個節點查看資料流
注意事項:
- 測試模式用一次就會失效,每次測試前都要重新點「Listen for test event」
- 只適合開發階段除錯用,不適合長時間對外提供服務
適合情境:workflow 已經開發完成,要讓外部系統(例如前端)呼叫。
步驟:
- 開啟 workflow,右上角點擊 「Publish」 讓 workflow 進入 Published 狀態
- 不需要點開任何節點、不需要等待監聽畫面
- 直接在終端機執行(注意路徑是
webhook,沒有-test):
curl -X POST http://localhost:5678/webhook/query-assistant \
-H "Content-Type: application/json; charset=utf-8" \
--data-binary @payload.json- 這條路徑會持續監聽,可以無限次呼叫,不需要每次重新啟動
注意事項:
- 修改任何節點內容後,必須重新按一次 Publish,正式路徑才會套用新版本
- 忘記重新 Publish 的話,
webhook路徑跑的還是修改前的舊邏輯
| 症狀 | 原因 |
|---|---|
| 節點卡在「Executing previous nodes...」轉圈不動 | 打了正式網址(webhook),但編輯器還停在「Listening for test event」等測試事件;或反過來打了測試網址但沒點「Listen for test event」 |
Received request for unknown webhook |
呼叫的路徑跟目前監聽的模式(test / production)對不上 |
| 改了程式碼但結果沒變 | 忘記重新 Publish |
| 問題 | 原因 | 解法 |
|---|---|---|
| API 回傳 304 + 空 body | 瀏覽器快取標頭 If-None-Match |
加入 Cache-Control: no-cache |
| TanStack Query 無限重試 | res.json() 解析空 body 拋出錯誤 |
統一使用 fetchJSON helper |
Zod v4 + z.coerce.number() 型別錯誤 |
v4 型別推導改變 | 改用 z.number() + valueAsNumber |
| NVIDIA API Key 洩漏風險 | .env / workflow 備份檔可能被 commit |
.gitignore 排除、workflow 備份存放於 git 專案目錄外 |
| Vite Proxy 未生效 | 設定後未重啟 Vite | 修改 vite.config.ts 後需重啟 |
| json-server 併發寫入崩潰 | Promise.all 平行寫入衝突 |
改用 for...of 序列寫入 |
| n8n HTTP Request Header 錯誤 | Name/Value 欄位對調 | 確認 Authorization / Content-Type 各自獨立填入正確欄位 |
n8n 表達式 {{ }} 未被解析,AI 收到字面文字 |
複雜運算式塞在 Fixed 模式 JSON 字串中不穩定 | 新增 Code 節點預先組好 prompt,HTTP Request 改用 Expression 模式 {{ JSON.stringify({...}) }} |
| Docker 容器重啟後 n8n 工作流程消失 | Windows + Git Bash 下 ~/.n8n volume 路徑未正確持久化 |
不使用 --rm、養成手動 Export workflow JSON 備份至 git 專案目錄外的習慣 |
| n8n 容器連 json-server 連線被拒 | 容器內 localhost 指向容器自身,非宿主機 |
改用 host.docker.internal:3001 |
| NVIDIA NIM 70b 模型頻繁 504 逾時 | 免費層級對大模型限制較嚴格 | 改用 meta/llama-3.1-8b-instruct,穩定性明顯提升 |
| 前端瀏覽器分頁與 n8n 失去即時連線,節點執行卡住無回應 | WebSocket 連線在容器重啟後未正確恢復 | 關閉分頁重新連線,嚴重時重啟整個瀏覽器;同時為 n8n 的 HTTP Request 節點加上 timeout 設定,避免卡住無限等待 |
| 小型 AI 模型在 UNKNOWN 分類下偶爾不遵循固定回覆指令 | llama-3.1-8b 對簡短指令的遵循度較低 | 明確要求「請用繁體中文回覆」並提供具體範例句子,降低模型自由發揮的機率 |
最後更新:2026-07-13
已完成並驗證:
- 進銷存 CRUD 四大模組
- AI 商品文案生成
- Dashboard 儀表板(含 isError 處理、效能優化)
- Agent A:AI 補貨建議(已上線)
- Agent B:REVENUE_COMPARISON 意圖,六節點完整串接(Webhook → 意圖分類 → AI判斷 → 資料運算 → AI生成回答 → 回傳),已整合進
Dashboard.tsx前端 UI 並測試成功
下次接續開發的下一步:
- 新增
GENERAL_INVENTORY_QA意圖的資料撈取 Code 節點(撈取 products + ingredients 摘要,作為 AI 回答的上下文依據) - 實作
TOP_SELLING_PRODUCTS意圖(統計 salesOrders 中各商品銷售數量並排序) - 將查詢助理 UI 從 Dashboard 內嵌小卡片,視時間評估是否獨立成專屬頁面/聊天框元件
技術債:API 端點目前寫死在各 hook 檔案,應改用(已完成:集中於.env環境變數管理src/lib/config.ts)
林家齊 Ataya Lin
MIT License
