跳到主要內容跳到頁尾
作品
  • 一眼看到
  • 減錯

手上有統編、發票號或半個名字,都查得到同一個客戶

從客戶名稱、統編或發票等六種線索,查到同一個客戶總覽。工具只有查詢,沒有寫入路徑。

2026/07
6 種查法、1 個客戶總覽
案例閱讀約 5 分鐘
背景與使用技術(4 項)

同事手上的線索通常是一組統編、一張發票號,或一個只記得一半的客戶名稱,而這幾種都無法直接在計費系統裡篩選,查一家客戶得在後台來回切換好幾個畫面。做了一個唯讀的 Apps Script 網頁工具:提供六種查詢角度,全部收斂到同一個客戶總覽,訂閱、發票、交易與事件時間線一頁看完,且沒有任何能改動帳務資料的寫入路徑。

Google Apps Script計費系統 API唯讀設計快取

誰在用

公司的財務與營收營運同事

原本怎麼做

同事常問的是:這家客戶的訂閱還在嗎?這期的續約帳單開出去了沒?上個月那筆款項最後結算成什麼?答案都在計費系統裡,難的是線索對不上:系統只認它自己發的客戶編號,同事手上通常是統一編號、發票號碼,或一個只記得一半的客戶名稱。查一家客戶,就得先用名字猜,猜不到再換一個畫面試發票號,來回切換,最後常常還是要轉手去問人。

前後對比

手上有什麼線索改造前系統只認它自己的客戶編號,要先用名字猜改造後6 種查法:客戶名稱、登記名稱、統編、分支 ID、客戶 ID、發票號規格
看客戶改造前來回切換畫面改造後同一頁看訂閱與狀態、發票(可下載 PDF)、交易與事件時間線規格
風險改造前查錯客戶就可能改到帳務資料改造後沒有任何寫入路徑,全程唯讀規格

量測:實測=有計時或有紀錄;估計=喜特先生推估;客戶自述=客戶回報;規格=設計上的固定條件;待補=尚未取得數字。

流程一眼看懂

改造前
  1. 手上有統編或半個客戶名稱

  2. 用名字猜客戶

  3. 換畫面試發票號

  4. 還是要問人

改造後
  1. 選一種線索

  2. 對照內部對照表

  3. 同一個客戶總覽

  4. 訂閱、發票、交易與事件

流程示意:依本案既有做法整理,非客戶實際畫面。

做了什麼

  1. 唯讀:後端沒有任何寫入路徑,查錯人也不會改到資料。
  2. 六個查詢角度各自一個分頁,讓同事自己選,不讓程式猜類型後再除錯。
  3. 系統查不到的線索(客戶名稱、登記名稱、統編),由一張自己維護的對照表補上。
  4. 不論從哪個角度進來,最後都停在同一個客戶總覽。

程式碼與實作細節收在頁面下方,需要時再展開。

你的情況像這樣嗎?

  • 同事手上的線索(統編、發票號、半個客戶名稱)和系統認的編號對不上。
  • 查資料的工具同時有改資料的權限,讓人不太敢隨便點。
  • 你想要的是「查得到」,不是再多一個要訓練的後台。

更多成果與運作條件

  • 6 種查法,1 個客戶總覽

    統編、發票號、分支 ID 或只記得一半的客戶名稱,都停在同一頁的訂閱、發票、交易與事件時間線

  • 快取有效期 300 秒與 1800 秒

    搜尋與客戶總覽依變動速度用 300 秒,發票、明細與對照表索引用 1800 秒

  • 沒有寫入路徑

    取代的是「查錯客戶就可能改到帳務資料」的風險;查詢省下的時間尚未實測

以上為本案既有成果與運作條件;估計值依原始資料標示,其他專案需另行評估,並非通用成效保證。

六種線索,同一份總覽

從手邊已有的線索查詢,最後查看同一個客戶的訂閱、發票與交易。

畫面示意・範例資料,非客戶實際截圖。手機可左右滑動查看。

六種線索,同一份總覽(示意):從手邊已有的線索查詢,最後查看同一個客戶的訂閱、發票與交易。

挑戰:原本遇到的困難

財務與營收營運的同事常問的是三種問題:這家客戶的訂閱還在嗎?這期的續約帳單開出去了沒?上個月那筆款項最後結算成什麼?

答案都在計費系統裡,難的是線索對不上。計費系統只認它自己發的客戶編號,而同事手上通常是統一編號、發票號碼,或一個只記得一半的客戶名稱。統編不能篩選,公司名稱只能從開頭比對,登記名稱在資料模型裡沒有對應的欄位。查一家客戶,就得先用名字猜,猜不到再換一個畫面試發票號,來回切換,最後常常還是要轉手去問人。

完整解決方案與設計取捨

做一個部署在公司網域下的 Google Apps Script 網頁工具,唯讀。前端是網頁(用 Apps Script 的 HtmlService 做成),後端是一層內建重試的計費系統用戶端,沒有任何寫入路徑。

查詢分六個角度,各自對應一種手上的線索。客戶名稱、營業登記名稱、統一編號,系統本身查不到,就從一張自己維護的對照表補上;分支 ID 與客戶 ID 直接查,因為它們本來就是系統的識別碼;發票號碼先反查客戶 ID;統一發票號先查對照表換成計費系統的發票號,再走發票號的路徑。

不論從哪個角度進來,最後都停在同一個客戶總覽:訂閱與狀態、發票(每筆可下載 PDF)、交易與事件時間線。搜尋頁用分頁籤切換查詢角度,不用單一輸入框自動判斷類型,因為統編與發票號的格式有時相近,讓同事自己選,比程式猜錯後再除錯省事。

技術與實作細節

展開完整實作說明

週二上午,一句「訂閱還在嗎」查了十幾分鐘

情境改寫:依這類查詢常見的狀況整理,細節為示意。

週二上午,群組裡有人問:「這家客戶的訂閱還在嗎?」問的人手上只有一個記得一半的客戶名稱。先打開計費系統後台,用名稱搜尋,沒有結果,因為後台的公司名稱欄只能從開頭比對,而他記錯了前半。接著改用發票號碼試,對不上。最後只能換到別的畫面一個一個比,前後大約十幾分鐘,大半時間花在切換畫面。

這一次還查得到。下一次換一個人、換一種線索,還是得從頭來。

人工查,為什麼總卡在同一個地方?

計費系統認人的方式只有它自己給的客戶 ID。同事手上通常是四種線索:招牌上的名字、合約上的營業登記名稱、統一編號、發票號碼。系統對這四種的支援程度不一樣。vat_number(統一編號欄位)完全不能用來篩選;公司名稱只支援 starts_with(只能比對開頭),只記得一半的名稱查出來是空的;登記名稱在資料模型裡沒有對應的欄位。

不能要求同事先學會系統的 ID 規則,所以缺的那幾個維度只能從外面補。人工查慢還是其次,更麻煩的是每種線索都要換一個入口,每換一次就要重新判斷這條線索屬於哪一種。

六個入口,各自接到同一個客戶 ID

六種線索各自接到計費系統的客戶 ID 上。有的能直接問系統,有的得先經過本地對照表。

線索計費系統能不能直接問這個工具怎麼接
客戶名稱只能前綴比對,記不全就查不到掃對照表的客戶名稱欄(子字串比對),每個分支 ID 各查一次客戶
營業登記名稱沒有這個概念掃同一張對照表的登記名稱欄,獨立成一個查詢角度
統一編號不能用 vat_number 篩選查對照表解出分支 ID,一個統編可能對到多個分支,全部回傳
分支 ID/客戶 ID能,這是系統自己的識別碼正規化後用 id[starts_with] 直接查
發票號碼能,發票端點可以反查查 /invoices/{id} 取得 customer_id,再載入客戶總覽
統一發票號不認得,它是另一套號碼查統一發票對照表換成計費系統的發票號,再走發票號的路徑

六列查詢線索各自經過不同入口,右側合併成一格的客戶總覽是所有線索的終點,被選取的就是這一格← 左右滑動看完整的圖,點圖可放大

圖:六種線索經過的入口不同,最後都停在同一個客戶總覽。

對照表要怎麼讀,表變長後才不會壞?

對照表的欄位是 branch ID | 統編 | 客戶名稱 | 營業登記名稱,前四個查詢角度都靠它把線索解析回分支 ID。

讀表有兩種做法:整張載入記憶體一次比對,或逐行搜尋。這裡選逐行搜尋。Apps Script 的 CacheService 單一值上限是 100KB,表一長,整張載入很容易撐爆快取。逐行搜尋慢一些,但表變長後不會突然失效。

分支 ID 寫法不一,容錯要放在哪一層?

同一個分支,同事貼進來的寫法可能是 -AbC123、AbC123,或標準的 B-AbC123。計費系統慣用的是 B-AbC123。程式認得這些寫法,收斂回標準形,同事就不必為了格式去改寫。下面是簡化過的示意寫法:

Apps Script
function normalizeBranchId(input) {
const id = input.trim().replace(/^-+/, '');  // 去掉多打的減號
return id.startsWith('B-') ? id : 'B-' + id; // 補上 B- 前綴
}
// normalizeBranchId('-AbC123')  -> 'B-AbC123'
// normalizeBranchId('AbC123')   -> 'B-AbC123'
// normalizeBranchId('B-AbC123') -> 'B-AbC123'

轉換完成後,BillingClient.fetchAll() 會同時發出客戶基本資料、訂閱、發票三個 GET 請求,三個一起等,不必等客戶資料回來才發下一個。事件時間線(/events)沒有放進這一批,因為它的 content_snapshot 欄位很肥,常常失敗。如果把它放進同一批,一個 events 失敗就會連累整個總覽,所以它獨立取得,失敗時只影響時間線那一區。

快取多久,重試幾次?

TTL(快取的有效秒數,過了就重新向系統拿)依資料變動速度分兩級。所有快取 key 都帶固定前綴,不同查詢條件換不同 key,結果不會互相污染。搜尋與客戶總覽變動快,設 300 秒;發票對應、明細與 VAT/統一發票索引變動慢,設 1800 秒。

重試的分界線是錯誤碼。網路失敗、429、5xx 做三層重試,延遲依序是 5、10、15 秒。4xx(429 除外)立刻拋錯。示意如下:

Apps Script
const RETRY_DELAYS = [5, 10, 15];           // 秒,最多三層
function shouldRetry(status) {
if (status === 429 || status >= 500) return true; // 對方忙,過幾秒再送可能成功
return false;                                    // 4xx:我方送錯,重送一樣錯
}
注意

客戶總覽加入每張發票的明細之後,如果沿用舊的 cache key,舊快取值就會在 300 秒的有效期內,被當成新結構讀出來,前端解析會壞掉,而且要等快取過期才會恢復。所以把 key 加上版號 v2,不去賭舊值相不相容。

誰查得到,出錯時誰先知道?

前端用一個 DOM 工廠函式建立節點,所有文字都用 textContent 寫入,所以計費系統回傳的資料不會被當成 HTML 插進頁面,也就沒有 XSS(惡意程式碼注入)的入口。

網頁入口有網域閘,只放行公司網域的帳號,其餘回傳 403。每個 API 入口再經過 assertAuthorized_() 檢查,並包進共用 Apps Script 函式庫的 tryRun()。出錯時,錯誤會自動送進 Slack,所以頁面出狀況時,維護者會先收到通知,不必等同事來反映。

說明

這個工具只查詢計費系統,不會更動客戶設定或帳務資料,設計上沒有任何寫入路徑。

查不到的客戶,要從哪裡找?

代價也要說清楚:客戶名稱、營業登記名稱、統一編號與統一發票號四個角度,完整度就等於對照表的完整度。沒有被加進對照表的客戶,從這四個角度查不到,只能改用發票號或分支 ID。發票號與分支 ID 不依賴對照表,所以這兩條路永遠可用。

查詢省下多少時間沒有量測,目前是尚未實測。能確定的是同一個問題不再需要轉手:以前同事問不到就要去問維護者,現在多數情況自己就查完了。

延伸閱讀

有類似的情境想討論

帶著現有流程與去識別範例,討論適合的做法。